foreman_rh_cloud 5.0.32 → 5.0.33

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/rh_cloud/cloud_request_controller.rb +83 -0
  3. data/app/controllers/foreman_inventory_upload/uploads_controller.rb +7 -0
  4. data/app/models/setting/rh_cloud.rb +2 -1
  5. data/app/services/foreman_rh_cloud/cloud_presence.rb +124 -0
  6. data/app/services/foreman_rh_cloud/cloud_request.rb +8 -1
  7. data/app/services/foreman_rh_cloud/hit_remediations_retriever.rb +67 -0
  8. data/app/services/foreman_rh_cloud/remediations_retriever.rb +16 -45
  9. data/app/services/foreman_rh_cloud/template_renderer_helper.rb +13 -1
  10. data/app/services/foreman_rh_cloud/url_remediations_retriever.rb +37 -0
  11. data/app/views/job_templates/rh_cloud_download_playbook.erb +26 -0
  12. data/config/routes.rb +2 -0
  13. data/lib/foreman_rh_cloud/engine.rb +13 -2
  14. data/lib/foreman_rh_cloud/version.rb +1 -1
  15. data/lib/foreman_rh_cloud.rb +4 -0
  16. data/lib/insights_cloud/async/connector_playbook_execution_reporter_task.rb +193 -0
  17. data/lib/insights_cloud/generators/playbook_progress_generator.rb +49 -0
  18. data/lib/tasks/insights.rake +13 -0
  19. data/package.json +1 -1
  20. data/test/controllers/insights_cloud/api/cloud_request_controller_test.rb +78 -0
  21. data/test/jobs/connector_playbook_execution_reporter_task_test.rb +207 -0
  22. data/test/unit/playbook_progress_generator_test.rb +75 -0
  23. data/test/unit/services/foreman_rh_cloud/{remediations_retriever_test.rb → hit_remediations_retriever_test.rb} +3 -3
  24. data/test/unit/services/foreman_rh_cloud/url_remediations_retriever_test.rb +27 -0
  25. metadata +19 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5991354b7fcd361491a962af732a7543402245af2516964876fee68b1bfd6e07
4
- data.tar.gz: 6232fd7c367092e0afb3591c313f3347c751610fa2a3b4d38e2f59de08cd2313
3
+ metadata.gz: bc3c3d4f7552bd3c599539ccb241473e38654c058ad4951e0e668b20d4648c67
4
+ data.tar.gz: 4545a0307c6eb685c2f84543530af13a25c984d605b9fcb01b1e3eb82c3618f2
5
5
  SHA512:
6
- metadata.gz: d38647081b45704673ed8c77b51ade6e90ee8955bf5bd8433bdca9f3072667d505b314ece193e1dd488af51c8cdf6396de5d987acb718693e6c0cecd7e1c7a18
7
- data.tar.gz: 2622868402ef0d3a0fa55d74652e1985b97c95b1dd42bab865e86dd8b68929e95ebd5dd1bc3bba8fb9fef2fa8d7e4af23b9f00c959d5aca8eb4d4ee180722fb6
6
+ metadata.gz: a147e2ceb11d2834af97ca954ea8c839bfe663a684fdac16e309ee4f003d3509f38dde84b75e3b06d837de390336541eb7b6c08259dfa9871d681a98cc717363
7
+ data.tar.gz: b176f027e036d865cdf31541af29fc09487407e3f8c95e83bb2c76a3989360e35939762e52de75d0a9df260eb7931ac5b0ca69fa32fe88d496896893103c2a9d
@@ -0,0 +1,83 @@
1
+ module Api::V2::RhCloud
2
+ class CloudRequestController < ::Api::V2::BaseController
3
+ layout false
4
+
5
+ KNOWN_DIRECTIVES = {
6
+ 'playbook-sat' => :handle_run_playbook_request,
7
+ 'foreman_rh_cloud' => :handle_run_playbook_request,
8
+ }
9
+
10
+ def update
11
+ handler = KNOWN_DIRECTIVES[directive]
12
+
13
+ unless handler
14
+ render json: {
15
+ :message => "No valid handler is found for directive: #{directive}",
16
+ }, status: :bad_request
17
+ return
18
+ end
19
+
20
+ send(handler)
21
+
22
+ render json: {
23
+ :message => "Handled #{directive} by #{handler}",
24
+ }
25
+ end
26
+
27
+ private
28
+
29
+ def metadata
30
+ params['metadata']
31
+ end
32
+
33
+ def content
34
+ # the content received as base 64 of the string in double quotes
35
+ Base64.decode64(params['content']).tr('"', '')
36
+ end
37
+
38
+ def directive
39
+ params['directive']
40
+ end
41
+
42
+ def handle_run_playbook_request
43
+ logger.error("API token is not set, unable to fetch data from the cloud") && return if Setting[:rh_cloud_token].empty?
44
+ logger.error("Playbook URL is not valid: #{content}") && return unless valid_url?(content)
45
+ logger.error("Reporting URL is not valid: #{metadata['return_url']}") && return unless valid_url?(metadata['return_url'])
46
+
47
+ hosts = metadata['hosts'].split(',')
48
+ host_ids = host_ids(hosts)
49
+
50
+ logger.warn("Some hosts were not found. Looked for: #{hosts}, found ids: #{host_ids}") unless host_ids.length == hosts.length
51
+
52
+ logger.error("sat_org_id is not present in the metadata") && return unless metadata['sat_org_id']
53
+ org_id = metadata['sat_org_id'].to_i
54
+ organization = Organization.find(org_id)
55
+
56
+ composer = nil
57
+ Organization.as_org(organization) do
58
+ composer = ::JobInvocationComposer.for_feature(
59
+ :rh_cloud_connector_run_playbook,
60
+ host_ids,
61
+ {
62
+ playbook_url: content,
63
+ report_url: metadata['return_url'],
64
+ report_interval: metadata['response_interval'].to_i,
65
+ correlation_id: metadata['correlation_id'],
66
+ }
67
+ )
68
+ composer.trigger!
69
+ end
70
+
71
+ composer.job_invocation
72
+ end
73
+
74
+ def valid_url?(url)
75
+ parsed = URI(url)
76
+ ForemanRhCloud.cloud_url_validator.match(parsed.host)
77
+ end
78
+
79
+ def host_ids(hosts)
80
+ InsightsFacet.where(uuid: hosts).pluck(:host_id)
81
+ end
82
+ end
83
+ end
@@ -18,6 +18,13 @@ module ForemanInventoryUpload
18
18
  end
19
19
 
20
20
  def enable_cloud_connector
21
+ Organization.unscoped.each do |org|
22
+ presence = ForemanRhCloud::CloudPresence.new(org, logger)
23
+ presence.announce_to_sources
24
+ rescue StandardError => ex
25
+ logger.warn(ex)
26
+ end
27
+
21
28
  cloud_connector = ForemanRhCloud::CloudConnector.new
22
29
  render json: cloud_connector.install.to_json
23
30
  end
@@ -1,5 +1,5 @@
1
1
  class Setting::RhCloud < Setting
2
- ::Setting::BLANK_ATTRS.concat %w{rh_cloud_token}
2
+ ::Setting::BLANK_ATTRS.concat %w{rh_cloud_token rhc_instance_id}
3
3
 
4
4
  def self.load_defaults
5
5
  return false unless table_exists?
@@ -20,6 +20,7 @@ class Setting::RhCloud < Setting
20
20
  set('rh_cloud_token', N_('Authentication token to Red Hat cloud services. Used to authenticate requests to cloud APIs'), nil, N_('Red Hat Cloud token'), nil, encrypted: true),
21
21
  set('exclude_installed_packages', N_('Exclude installed packages from being uploaded to the Red Hat cloud'), false, N_("Exclude installed Packages")),
22
22
  set('include_parameter_tags', N_('Should import include parameter tags from Foreman?'), false, N_('Include parameters in insights-client reports')),
23
+ set('rhc_instance_id', N_('RHC daemon id'), nil, N_('ID of the RHC(Yggdrasil) daemon')),
23
24
  ]
24
25
  end
25
26
 
@@ -0,0 +1,124 @@
1
+ module ForemanRhCloud
2
+ class CloudPresence
3
+ include InsightsCloud::CandlepinCache
4
+ include ForemanRhCloud::CloudRequest
5
+
6
+ attr_reader :organization, :logger
7
+
8
+ def initialize(organization, logger)
9
+ @organization = organization
10
+ @logger = logger
11
+ end
12
+
13
+ def announce_to_sources
14
+ register_rhc_instance
15
+ end
16
+
17
+ def satellite_source_type
18
+ @satellite_source_type ||= begin
19
+ source_type_response = JSON.parse(
20
+ execute_cloud_request(
21
+ method: :get,
22
+ url: source_type_url,
23
+ headers: {
24
+ content_type: :json,
25
+ },
26
+ ssl_client_cert: OpenSSL::X509::Certificate.new(certs[:cert]),
27
+ ssl_client_key: OpenSSL::PKey::RSA.new(certs[:key])
28
+ )
29
+ )
30
+
31
+ source_type_response['data'].first['id']
32
+ end
33
+ end
34
+
35
+ def satellite_instance_source
36
+ @satellite_instance_source ||= begin
37
+ source_response = JSON.parse(
38
+ execute_cloud_request(
39
+ method: :get,
40
+ url: satellite_instance_source_url,
41
+ headers: {
42
+ content_type: :json,
43
+ },
44
+ ssl_client_cert: OpenSSL::X509::Certificate.new(certs[:cert]),
45
+ ssl_client_key: OpenSSL::PKey::RSA.new(certs[:key])
46
+ )
47
+ )
48
+
49
+ result = source_response['data'].first
50
+ result&.dig('id')
51
+ end
52
+ end
53
+
54
+ def create_satellite_instance_source
55
+ create_response = JSON.parse(
56
+ execute_cloud_request(
57
+ method: :post,
58
+ url: create_satellite_instance_source_url,
59
+ headers: {
60
+ content_type: :json,
61
+ },
62
+ ssl_client_cert: OpenSSL::X509::Certificate.new(certs[:cert]),
63
+ ssl_client_key: OpenSSL::PKey::RSA.new(certs[:key]),
64
+ payload: {
65
+ name: "satellite: #{Foreman.instance_id} org: #{@organization.name}",
66
+ source_ref: Foreman.instance_id,
67
+ source_type_id: satellite_source_type,
68
+ }.to_json
69
+ )
70
+ )
71
+
72
+ @satellite_instance_source = create_response['id']
73
+ end
74
+
75
+ def register_rhc_instance
76
+ raise Foreman::Exception.new('rhc_instance_id is empty, cannot register RHC to the cloud') if Setting[:rhc_instance_id].empty?
77
+ source_id = satellite_instance_source || create_satellite_instance_source
78
+
79
+ create_response = JSON.parse(
80
+ execute_cloud_request(
81
+ method: :post,
82
+ url: create_rhc_connections_url,
83
+ headers: {
84
+ content_type: :json,
85
+ },
86
+ ssl_client_cert: OpenSSL::X509::Certificate.new(certs[:cert]),
87
+ ssl_client_key: OpenSSL::PKey::RSA.new(certs[:key]),
88
+ payload: {
89
+ source_id: source_id,
90
+ rhc_id: Setting[:rhc_instance_id],
91
+ }.to_json
92
+ )
93
+ )
94
+
95
+ @satellite_instance_source = create_response['id']
96
+ end
97
+
98
+ private
99
+
100
+ def sources_url(path)
101
+ "#{ForemanRhCloud.cert_base_url}/api/sources/v3.1#{path}"
102
+ end
103
+
104
+ def source_type_url
105
+ sources_url('/source_types?filter[name]=satellite')
106
+ end
107
+
108
+ def satellite_instance_source_url
109
+ sources_url("/sources?filter[source_ref]=#{Foreman.instance_id}")
110
+ end
111
+
112
+ def create_satellite_instance_source_url
113
+ sources_url('/sources')
114
+ end
115
+
116
+ def create_rhc_connections_url
117
+ sources_url('/rhc_connections')
118
+ end
119
+
120
+ def certs
121
+ @certs ||= candlepin_id_cert(@organization)
122
+ end
123
+ end
124
+ end
@@ -8,7 +8,14 @@ module ForemanRhCloud
8
8
  proxy: ForemanRhCloud.transformed_http_proxy_string(logger: logger),
9
9
  }.deep_merge(params)
10
10
 
11
- RestClient::Request.execute(final_params)
11
+ response = RestClient::Request.execute(final_params)
12
+
13
+ logger.debug("Response headers for request url #{final_params[:url]} are: #{response.headers}")
14
+
15
+ response
16
+ rescue RestClient::Exception => ex
17
+ logger.debug("Failed response with code #{ex.http_code} headers for request url #{final_params[:url]} are: #{ex.http_headers} and body: #{ex.http_body}")
18
+ raise ex
12
19
  end
13
20
  end
14
21
  end
@@ -0,0 +1,67 @@
1
+ module ForemanRhCloud
2
+ class HitRemediationsRetriever < RemediationsRetriever
3
+ def initialize(hit_remediation_pairs, logger: Logger.new(IO::NULL))
4
+ super(logger: logger)
5
+ @hit_remediation_pairs = hit_remediation_pairs
6
+ logger.debug("Querying playbook for #{hit_remediation_pairs}")
7
+ end
8
+
9
+ private
10
+
11
+ def hit_ids
12
+ @hit_remediation_pairs.map { |pair| pair["hit_id"] }
13
+ end
14
+
15
+ def remediation_ids
16
+ @hit_remediation_pairs.map { |pair| pair["resolution_id"] }
17
+ end
18
+
19
+ def hits
20
+ @hits ||= Hash[
21
+ InsightsHit.joins(:insights_facet).where(id: hit_ids).pluck(:id, 'insights_facets.uuid')
22
+ ]
23
+ end
24
+
25
+ def pairs_by_remediation_id
26
+ @hit_remediation_pairs.group_by { |pair| pair["resolution_id"] }
27
+ end
28
+
29
+ def remediations
30
+ @remediations ||= Hash[
31
+ InsightsResolution.where(id: remediation_ids).pluck(:id, :resolution_type, :rule_id).map do |id, resolution_type, rule_id|
32
+ [id, {resolution_type: resolution_type, rule_id: rule_id}]
33
+ end
34
+ ]
35
+ end
36
+
37
+ def playbook_request
38
+ {
39
+ issues: pairs_by_remediation_id.map do |remediation_id, pairs|
40
+ {
41
+ resolution: remediations[remediation_id][:resolution_type],
42
+ id: InsightsCloud.remediation_rule_id(remediations[remediation_id][:rule_id]),
43
+ systems: pairs.map do |pair|
44
+ hits[pair["hit_id"]]
45
+ end,
46
+ }
47
+ end,
48
+ }
49
+ end
50
+
51
+ def playbook_url
52
+ InsightsCloud.playbook_url
53
+ end
54
+
55
+ def headers
56
+ super
57
+ end
58
+
59
+ def payload
60
+ playbook_request.to_json
61
+ end
62
+
63
+ def method
64
+ :post
65
+ end
66
+ end
67
+ end
@@ -4,11 +4,8 @@ module ForemanRhCloud
4
4
 
5
5
  attr_reader :logger
6
6
 
7
- def initialize(hit_remediation_pairs, logger: Logger.new(IO::NULL))
8
- @hit_remediation_pairs = hit_remediation_pairs
7
+ def initialize(logger: Logger.new(IO::NULL))
9
8
  @logger = logger
10
-
11
- logger.debug("Querying playbook for #{hit_remediation_pairs}")
12
9
  end
13
10
 
14
11
  def create_playbook
@@ -26,55 +23,29 @@ module ForemanRhCloud
26
23
 
27
24
  private
28
25
 
29
- def hit_ids
30
- @hit_remediation_pairs.map { |pair| pair["hit_id"] }
31
- end
32
-
33
- def remediation_ids
34
- @hit_remediation_pairs.map { |pair| pair["resolution_id"] }
35
- end
36
-
37
- def hits
38
- @hits ||= Hash[
39
- InsightsHit.joins(:insights_facet).where(id: hit_ids).pluck(:id, 'insights_facets.uuid')
40
- ]
41
- end
42
-
43
- def pairs_by_remediation_id
44
- @hit_remediation_pairs.group_by { |pair| pair["resolution_id"] }
26
+ def query_playbook
27
+ execute_cloud_request(
28
+ method: method,
29
+ url: playbook_url,
30
+ headers: headers,
31
+ payload: payload
32
+ )
45
33
  end
46
34
 
47
- def remediations
48
- @remediations ||= Hash[
49
- InsightsResolution.where(id: remediation_ids).pluck(:id, :resolution_type, :rule_id).map do |id, resolution_type, rule_id|
50
- [id, {resolution_type: resolution_type, rule_id: rule_id}]
51
- end
52
- ]
35
+ def playbook_url
53
36
  end
54
37
 
55
- def playbook_request
38
+ def headers
56
39
  {
57
- issues: pairs_by_remediation_id.map do |remediation_id, pairs|
58
- {
59
- resolution: remediations[remediation_id][:resolution_type],
60
- id: InsightsCloud.remediation_rule_id(remediations[remediation_id][:rule_id]),
61
- systems: pairs.map do |pair|
62
- hits[pair["hit_id"]]
63
- end,
64
- }
65
- end,
40
+ content_type: :json,
66
41
  }
67
42
  end
68
43
 
69
- def query_playbook
70
- execute_cloud_request(
71
- method: :post,
72
- url: InsightsCloud.playbook_url,
73
- headers: {
74
- content_type: :json,
75
- },
76
- payload: playbook_request.to_json
77
- )
44
+ def payload
45
+ end
46
+
47
+ def method
48
+ :get
78
49
  end
79
50
  end
80
51
  end
@@ -15,8 +15,20 @@ module ForemanRhCloud
15
15
  end
16
16
  def remediations_playbook(hit_remediation_pairs)
17
17
  hit_remediation_pairs = JSON.parse(hit_remediation_pairs)
18
- retriever = ForemanRhCloud::RemediationsRetriever.new(hit_remediation_pairs, logger: template_logger)
18
+ retriever = ForemanRhCloud::HitRemediationsRetriever.new(hit_remediation_pairs, logger: template_logger)
19
19
  retriever.create_playbook
20
20
  end
21
+
22
+ apipie :method, 'Returns a Red Hat remediation playbook compiled on console.redhat.com' do
23
+ required :remediation_path, String, desc: ''
24
+ returns String, desc: 'Playbook downloaded from the cloud'
25
+ end
26
+ def download_rh_playbook(playbook_url)
27
+ retriever = ForemanRhCloud::UrlRemediationsRetriever.new(url: playbook_url, logger: template_logger)
28
+
29
+ cached("rh_playbook_#{playbook_url}") do
30
+ retriever.create_playbook
31
+ end
32
+ end
21
33
  end
22
34
  end
@@ -0,0 +1,37 @@
1
+ module ForemanRhCloud
2
+ class UrlRemediationsRetriever < RemediationsRetriever
3
+ attr_reader :url, :payload, :headers
4
+
5
+ def initialize(url:, payload: '', headers: {}, logger: Logger.new(IO::NULL))
6
+ super(logger: logger)
7
+
8
+ @url = url
9
+ @payload = payload
10
+ @headers = headers
11
+ end
12
+
13
+ private
14
+
15
+ def query_playbook
16
+ logger.debug("Querying playbook at: #{url} with payload: #{payload} and headers: #{headers}")
17
+
18
+ super
19
+ end
20
+
21
+ def playbook_url
22
+ @url
23
+ end
24
+
25
+ def headers
26
+ super.deep_merge(@headers)
27
+ end
28
+
29
+ def payload
30
+ @payload.to_json
31
+ end
32
+
33
+ def method
34
+ :get
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,26 @@
1
+ <%#
2
+ kind: job_template
3
+ name: Run a playbook downloaded from RH cloud
4
+ job_category: Red Hat Insights
5
+ description_format: 'Playbook generated by RH Cloud'
6
+ feature: rh_cloud_connector_run_playbook
7
+ template_inputs:
8
+ - name: playbook_url
9
+ description: URL of the playbook to run
10
+ input_type: user
11
+ required: true
12
+ - name: report_url
13
+ description: URL for calling the report callback
14
+ input_type: user
15
+ required: true
16
+ - name: correlation_id
17
+ description: correlation ID used to identify the original calling message
18
+ input_type: user
19
+ required: true
20
+ - name: report_interval
21
+ description: Reporting interval for the task. A report will be generated every report_interval seconds.
22
+ input_type: user
23
+ required: true
24
+ provider_type: Ansible
25
+ %>
26
+ <%= download_rh_playbook(input('playbook_url')) %>
data/config/routes.rb CHANGED
@@ -58,6 +58,8 @@ Rails.application.routes.draw do
58
58
 
59
59
  namespace 'rh_cloud' do
60
60
  post 'enable_connector', to: 'inventory#enable_cloud_connector'
61
+
62
+ post 'cloud_request', to: 'cloud_request#update'
61
63
  end
62
64
  end
63
65
  end
@@ -76,9 +76,13 @@ module ForemanRhCloud
76
76
  },
77
77
  :resource_type => ::InsightsHit.name
78
78
  )
79
+ permission(
80
+ :dispatch_cloud_requests,
81
+ 'api/v2/rh_cloud/cloud_request': [:update]
82
+ )
79
83
  end
80
84
 
81
- plugin_permissions = [:view_foreman_rh_cloud, :generate_foreman_rh_cloud, :view_insights_hits]
85
+ plugin_permissions = [:view_foreman_rh_cloud, :generate_foreman_rh_cloud, :view_insights_hits, :dispatch_cloud_requests]
82
86
 
83
87
  role 'ForemanRhCloud', plugin_permissions, 'Role granting permissions to view the hosts inventory,
84
88
  generate a report, upload it to the cloud and download it locally'
@@ -116,7 +120,7 @@ module ForemanRhCloud
116
120
  end
117
121
 
118
122
  extend_template_helpers ForemanRhCloud::TemplateRendererHelper
119
- allowed_template_helpers :remediations_playbook
123
+ allowed_template_helpers :remediations_playbook, :download_rh_playbook
120
124
  end
121
125
 
122
126
  ::Katello::UINotifications::Subscriptions::ManifestImportSuccess.include ForemanInventoryUpload::Notifications::ManifestImportSuccessNotificationOverride if defined?(Katello)
@@ -145,6 +149,13 @@ module ForemanRhCloud
145
149
  description: N_('Run remediation playbook generated by Insights'),
146
150
  host_action_button: false
147
151
  )
152
+ RemoteExecutionFeature.register(
153
+ :rh_cloud_connector_run_playbook,
154
+ N_('Run RH Cloud playbook'),
155
+ description: N_('Run playbook genrated by Red Hat remediations app'),
156
+ host_action_button: false,
157
+ provided_inputs: ['playbook_url', 'report_url', 'correlation_id', 'report_interval']
158
+ )
148
159
  # skip object creation when admin user is not present, for example in test DB
149
160
  if User.unscoped.find_by_login(User::ANONYMOUS_ADMIN).present?
150
161
  ::ForemanTasks.dynflow.config.on_init(false) do |world|
@@ -1,3 +1,3 @@
1
1
  module ForemanRhCloud
2
- VERSION = '5.0.32'.freeze
2
+ VERSION = '5.0.33'.freeze
3
3
  end
@@ -121,4 +121,8 @@ module ForemanRhCloud
121
121
  def self.legacy_insights_ca
122
122
  "#{ForemanRhCloud::Engine.root}/config/rh_cert-api_chain.pem"
123
123
  end
124
+
125
+ def self.cloud_url_validator
126
+ @cloud_url_validator ||= Regexp.new(ENV['SATELLITE_RH_CLOUD_VALIDATOR'] || 'redhat.com$')
127
+ end
124
128
  end