Skip to content

Add Advisor and Vulnerabilities to Foreman ping #1042

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from

Conversation

jeremylenz
Copy link
Collaborator

@jeremylenz jeremylenz commented Jul 24, 2025

What are the changes introduced in this pull request?

Add a ForemanRhCloud::Ping model to ::Foreman::Ping.

Considerations taken when implementing this change?

I am currently pinging only two URLs (see the code). Please let me know if there are any I should add, or change.

What are the testing steps for this pull request?

First you need a dev environment with with IoP services running.

You can see the list of services with

sudo foreman-maintain service status --brief

You should see all the IoP services:

$ sudo foreman-maintain service status --brief
Running Status Services
================================================================================
Get status of applicable services: 

Displaying the following service(s):
redis, postgresql, iop-core-engine, iop-core-gateway, iop-core-host-inventory, iop-core-host-inventory-api, iop-core-host-inventory-migrate, iop-core-ingress, iop-core-kafka, iop-core-puptoo, iop-core-yuptoo, iop-service-advisor-backend-api, iop-service-advisor-backend-service, iop-service-remediations-api, iop-service-vmaas-reposcan, iop-service-vmaas-webapp-go, iop-service-vuln-dbupgrade, iop-service-vuln-evaluator-recalc, iop-service-vuln-evaluator-upload, iop-service-vuln-grouper, iop-service-vuln-listener, iop-service-vuln-manager, iop-service-vuln-taskomatic, pulpcore-api, pulpcore-content, [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], tomcat, httpd, foreman-proxy
| displaying redis                                 [OK]                         
| displaying postgresql                            [OK]                         
/ displaying iop-core-engine                       [OK]                         
/ displaying iop-core-gateway                      [OK]                         
/ displaying iop-core-host-inventory               [OK]                         
/ displaying iop-core-host-inventory-api           [OK]                         
/ displaying iop-core-host-inventory-migrate       [OK]                         
/ displaying iop-core-ingress                      [OK]                         
/ displaying iop-core-kafka                        [OK]                         
/ displaying iop-core-puptoo                       [OK]                         
/ displaying iop-core-yuptoo                       [OK]                         
/ displaying iop-service-advisor-backend-api       [OK]                         
/ displaying iop-service-advisor-backend-service   [OK]                         
/ displaying iop-service-remediations-api          [OK]                         
/ displaying iop-service-vmaas-reposcan            [OK]                         
/ displaying iop-service-vmaas-webapp-go           [OK]                         
/ displaying iop-service-vuln-dbupgrade            [OK]                         
/ displaying iop-service-vuln-evaluator-recalc     [OK]                         
/ displaying iop-service-vuln-evaluator-upload     [OK]                         
/ displaying iop-service-vuln-grouper              [OK]                         
/ displaying iop-service-vuln-listener             [OK]                         
/ displaying iop-service-vuln-manager              [OK]                         
/ displaying iop-service-vuln-taskomatic           [OK]                         
/ displaying pulpcore-api                          [OK]                         
/ displaying pulpcore-content                      [OK]                         
/ displaying [email protected]             [OK]                         
/ displaying [email protected]             [OK]                         
/ displaying [email protected]             [OK]                         
/ displaying [email protected]             [OK]                         
/ displaying [email protected]             [OK]                         
/ displaying [email protected]             [OK]                         
/ displaying tomcat                                [OK]                         
/ displaying httpd                                 [OK]                         
/ displaying foreman-proxy                         [OK]                         
/ All services are running                                            [OK]      
--------------------------------------------------------------------------------

Until you have the IoP services, don't continue.

In bundle exec rails console:

pry(main)> ::Ping.ping
...
{:foreman=>{:database=>{:active=>true, :duration_ms=>"1"}, :cache=>{:servers=>[{:status=>"ok", :duration_ms=>"0"}]}},
 :foreman_rh_cloud=>{:services=>{:advisor=>{:status=>"ok", :duration_ms=>"34"}, :vulnerability=>{:status=>"ok", :duration_ms=>"15"}}, :status=>"ok"},
 :katello=>
  {:services=>
    {:candlepin=>{:status=>"ok", :duration_ms=>"21"},
     :candlepin_auth=>{:status=>"ok", :duration_ms=>"21"},
     :foreman_tasks=>{:status=>"ok", :duration_ms=>"251"},
     :katello_events=>{:status=>"ok", :message=>"0 Processed, 0 Failed", :duration_ms=>"2"},
     :candlepin_events=>{:status=>"ok", :message=>"0 Processed, 0 Failed", :duration_ms=>"0"},
     :pulp3=>{:status=>"ok", :duration_ms=>"56"},
     :pulp3_content=>{:status=>"ok", :duration_ms=>"469"}},
   :status=>"ok"}}

You should see the :foreman_rh_cloud services. also try

[2] pry(main)> ForemanRhCloud::Ping.ping
=> {:services=>{:advisor=>{:status=>"ok", :duration_ms=>"26"}, :vulnerability=>{:status=>"ok", :duration_ms=>"15"}}, :status=>"ok"}

You can test the FAIL state by stopping a service, such as

sudo systemctl stop iop-service-advisor-backend-api

Summary by Sourcery

Add a new Ping model and integrate ping/status endpoints for advisor and vulnerabilities services

New Features:

  • Introduce ForemanRhCloud::Ping model to perform health checks against advisor and vulnerabilities services
  • Register ping and status extensions into the ForemanRhCloud engine to expose these checks

Copy link

sourcery-ai bot commented Jul 24, 2025

Reviewer's Guide

Introduce a ForemanRhCloud::Ping model to perform health checks against advisor and vulnerability services and wire it into the plugin via ping and status extensions.

Sequence diagram for registering ping and status extensions

sequenceDiagram
  participant Plugin as ForemanRhCloud::Plugin
  participant Ping as ForemanRhCloud::Ping
  Plugin->>Ping: register_ping_extension { Ping.ping }
  Plugin->>Ping: register_status_extension { Ping.status }
Loading

File-Level Changes

Change Details Files
Add Ping model for advisor and vulnerability health checks
  • Define SERVICE_URLS with advisor and vulnerability endpoints
  • Implement ping, ping!, ping_services, ping_service orchestration methods
  • Create ping_url to perform SSL-enabled HTTP calls and parse JSON responses
  • Add status method returning version and UTC timestamp
  • Delegate exception handling to Katello::Ping.exception_watch
app/models/foreman_rh_cloud/ping.rb
Register ping and status extensions in the ForemanRhCloud plugin
  • Wrap registration in with_local_advisor_engine? conditional
  • Add register_ping_extension pointing to Ping.ping
  • Add register_status_extension pointing to Ping.status
lib/foreman_rh_cloud/plugin.rb

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@jeremylenz jeremylenz force-pushed the rh_cloud_ping branch 2 times, most recently from 62616cb to faca077 Compare July 30, 2025 20:19
@jeremylenz jeremylenz marked this pull request as ready for review July 31, 2025 20:52
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @jeremylenz - I've reviewed your changes - here's some feedback:

  • SERVICE_URLS currently defines the vulnerability endpoint as "/api//api/…" – please fix the duplicate path segment.
  • Consider extracting the SSL-enabled RestClient logic in ping_url into a reusable HTTP client helper to reduce duplication and improve testability.
  • The ping_services and ping! methods mix status computation and control flow – you might simplify by returning a single structured result object or raising specific exceptions for clearer error handling.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- SERVICE_URLS currently defines the vulnerability endpoint as "/api//api/…" – please fix the duplicate path segment.
- Consider extracting the SSL-enabled RestClient logic in ping_url into a reusable HTTP client helper to reduce duplication and improve testability.
- The ping_services and ping! methods mix status computation and control flow – you might simplify by returning a single structured result object or raising specific exceptions for clearer error handling.

## Individual Comments

### Comment 1
<location> `app/models/foreman_rh_cloud/ping.rb:8` </location>
<code_context>
+
+    SERVICE_URLS = {
+      :advisor => "https://localhost:24443/api/insights/v1/status/live/",
+      :vulnerability => "https://localhost:24443/api//api/vulnerability/v1/apistatus"
+    }
+
</code_context>

<issue_to_address>
Double slash in vulnerability service URL may cause issues.

The vulnerability service URL includes a double slash, which may cause unexpected server behavior. Please normalize the URL.
</issue_to_address>

### Comment 2
<location> `app/models/foreman_rh_cloud/ping.rb:63` </location>
<code_context>
+
+        options = {}
+        options[:ssl_ca_file] = ca_file unless ca_file.nil?
+        options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(File.read(Setting[:ssl_certificate]))
+        options[:ssl_client_key] = OpenSSL::PKey.read(File.read(Setting[:ssl_priv_key])) 
+
+        client = RestClient::Resource.new(url, options)
</code_context>

<issue_to_address>
No error handling for missing or invalid SSL certificate/key files.

Add error handling for missing or invalid certificate/key files to prevent unhandled exceptions and improve error reporting.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        options = {}
        options[:ssl_ca_file] = ca_file unless ca_file.nil?
        options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(File.read(Setting[:ssl_certificate]))
        options[:ssl_client_key] = OpenSSL::PKey.read(File.read(Setting[:ssl_priv_key])) 

        client = RestClient::Resource.new(url, options)
=======
        options = {}
        options[:ssl_ca_file] = ca_file unless ca_file.nil?
        begin
          cert_content = File.read(Setting[:ssl_certificate])
          key_content = File.read(Setting[:ssl_priv_key])
          options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(cert_content)
          options[:ssl_client_key] = OpenSSL::PKey.read(key_content)
        rescue Errno::ENOENT => e
          Rails.logger.error("SSL certificate or key file not found: #{e.message}")
          raise "SSL certificate or key file not found: #{e.message}"
        rescue OpenSSL::OpenSSLError => e
          Rails.logger.error("Invalid SSL certificate or key: #{e.message}")
          raise "Invalid SSL certificate or key: #{e.message}"
        end

        client = RestClient::Resource.new(url, options)
>>>>>>> REPLACE

</suggested_fix>

### Comment 3
<location> `app/models/foreman_rh_cloud/ping.rb:68` </location>
<code_context>
+
+        client = RestClient::Resource.new(url, options)
+
+        response = client.get
+        return {} if response.empty?
+        begin 
+          result = JSON.parse(response).with_indifferent_access
</code_context>

<issue_to_address>
Empty response check may not be robust for all RestClient responses.

Since not all RestClient responses implement `empty?`, check the response body or status code directly to prevent potential errors.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        response = client.get
        return {} if response.empty?
        begin 
          result = JSON.parse(response).with_indifferent_access
        rescue JSON::ParserError, NoMethodError
          result = { :response => response.body&.strip }
        end
        result
=======
        response = client.get
        # Robustly check for empty response body or non-200 status
        if response.nil? || (response.respond_to?(:body) && response.body.to_s.strip.empty?) || (response.respond_to?(:code) && response.code != 200)
          return {}
        end
        begin 
          result = JSON.parse(response).with_indifferent_access
        rescue JSON::ParserError, NoMethodError
          # Fallback: handle both String and RestClient::Response
          response_body = response.respond_to?(:body) ? response.body : response.to_s
          result = { :response => response_body&.strip }
        end
        result
>>>>>>> REPLACE

</suggested_fix>

### Comment 4
<location> `app/models/foreman_rh_cloud/ping.rb:70` </location>
<code_context>
+
+        response = client.get
+        return {} if response.empty?
+        begin 
+          result = JSON.parse(response).with_indifferent_access
+        rescue JSON::ParserError, NoMethodError
+          result = { :response => response.body&.strip }
+        end
</code_context>

<issue_to_address>
Catching NoMethodError may mask unrelated issues.

Rescue only the specific errors expected from JSON parsing to avoid hiding unrelated bugs.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +61 to +66
options = {}
options[:ssl_ca_file] = ca_file unless ca_file.nil?
options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(File.read(Setting[:ssl_certificate]))
options[:ssl_client_key] = OpenSSL::PKey.read(File.read(Setting[:ssl_priv_key]))

client = RestClient::Resource.new(url, options)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): No error handling for missing or invalid SSL certificate/key files.

Add error handling for missing or invalid certificate/key files to prevent unhandled exceptions and improve error reporting.

Suggested change
options = {}
options[:ssl_ca_file] = ca_file unless ca_file.nil?
options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(File.read(Setting[:ssl_certificate]))
options[:ssl_client_key] = OpenSSL::PKey.read(File.read(Setting[:ssl_priv_key]))
client = RestClient::Resource.new(url, options)
options = {}
options[:ssl_ca_file] = ca_file unless ca_file.nil?
begin
cert_content = File.read(Setting[:ssl_certificate])
key_content = File.read(Setting[:ssl_priv_key])
options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(cert_content)
options[:ssl_client_key] = OpenSSL::PKey.read(key_content)
rescue Errno::ENOENT => e
Rails.logger.error("SSL certificate or key file not found: #{e.message}")
raise "SSL certificate or key file not found: #{e.message}"
rescue OpenSSL::OpenSSLError => e
Rails.logger.error("Invalid SSL certificate or key: #{e.message}")
raise "Invalid SSL certificate or key: #{e.message}"
end
client = RestClient::Resource.new(url, options)


SERVICE_URLS = {
:advisor => "https://localhost:24443/api/insights/v1/status/live/",
:vulnerability => "https://localhost:24443/api/vulnerability/v1/apistatus"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to derive the base URL from the smart-proxy that represents the gateway allowing the https://localhost:24443 to be informed by the smart-proxy and not hard-coded.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further more, there are patterns for using the SmartProxy object we an lean on. This PR adds a method to fetch the iop smart-proxy object.

You could then use the Smart Proxy instance for the iop feature to pass into a ProxyAPI class for the iop smart-proxy where the properties, such as endpoints and specific requests are stored in a single classs creating a nice interface. An example from the Foreman code base:

https://github.com/theforeman/foreman/blob/develop/app/services/proxy_api/puppet_ca.rb

Which can be initialized like:

    @puppetca = ProxyAPI::PuppetCA.new :url => puppet_ca_proxy.url

There is likely other re-factorings in the code that could take advantage of this pattern. I think this PR and @nofaralfasi PR can synergize nicely with our Smart Proxy patterns.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Will wait for Nofar's PR to be in and then refactor.


def status
{
version: ForemanRhCloud::VERSION,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is status the unauthenticated one? If so, I think we should not return the version of the plugin. We've seen requests not to display version information in unauthenticated requests.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was copied from Katello ping. Should I also remove the version there?


SERVICE_URLS = {
:advisor => "https://localhost:24443/api/insights/v1/status/live/",
:vulnerability => "https://localhost:24443/api/vulnerability/v1/apistatus"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the hardcoded values be replaced by the URL from the Smart Proxy. This assumes binding on port 24443 on localhost.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants