foreman_rh_cloud 14.3.0 → 14.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/rh_cloud/cloud_request_controller.rb +1 -0
  3. data/app/controllers/api/v2/rh_cloud/inventory_controller.rb +11 -5
  4. data/app/controllers/foreman_inventory_upload/accounts_controller.rb +0 -1
  5. data/app/controllers/foreman_inventory_upload/uploads_controller.rb +0 -11
  6. data/app/controllers/foreman_inventory_upload/uploads_settings_controller.rb +0 -8
  7. data/app/services/foreman_rh_cloud/cloud_presence.rb +23 -0
  8. data/config/routes.rb +1 -3
  9. data/lib/foreman_rh_cloud/engine.rb +1 -6
  10. data/lib/foreman_rh_cloud/plugin.rb +3 -2
  11. data/lib/foreman_rh_cloud/version.rb +1 -1
  12. data/lib/insights_cloud/async/cloud_connector_announce_task.rb +76 -19
  13. data/package.json +1 -1
  14. data/test/controllers/inventory_upload/api/inventory_controller_test.rb +10 -8
  15. data/test/jobs/cloud_connector_announce_task_test.rb +116 -106
  16. data/webpack/ForemanInventoryUpload/Components/InventorySettings/MinimalInventoryDropdown.js +1 -0
  17. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageTitle.js +1 -0
  18. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/integrations.test.js +4 -12
  19. data/webpack/ForemanRhCloudPages.js +6 -0
  20. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsLabel.js +8 -7
  21. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableConstants.js +2 -2
  22. data/webpack/InsightsCloudSync/Components/InsightsTable/Pagination.js +1 -0
  23. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTableActions.test.js +2 -2
  24. data/webpack/InsightsCloudSync/Components/ToolbarDropdown.js +1 -0
  25. data/webpack/InsightsCloudSync/__tests__/InsightsCloudSyncActions.test.js +7 -5
  26. data/webpack/InsightsHostDetailsTab/InsightsTab.js +53 -39
  27. data/webpack/InsightsHostDetailsTab/InsightsTab.scss +7 -73
  28. data/webpack/InsightsHostDetailsTab/__tests__/InsightsTab.fixtures.js +7 -0
  29. data/webpack/InsightsHostDetailsTab/__tests__/InsightsTab.test.js +52 -30
  30. data/webpack/InsightsHostDetailsTab/components/ListItem/ListItem.js +43 -35
  31. data/webpack/InsightsHostDetailsTab/components/ListItem/__tests__/ListItem.test.js +111 -0
  32. data/webpack/InsightsVulnerabilityActionsBar/__tests__/InsightsVulnerabilityActionsBar.test.js +1 -5
  33. data/webpack/IopPathwayDetails/IopPathwayDetails.js +40 -0
  34. data/webpack/IopRecommendationDetails/IopRecommendationDetails.js +0 -18
  35. data/webpack/common/DropdownToggle.js +1 -0
  36. data/webpack/common/Switcher/HelpLabel.js +7 -5
  37. data/webpack/test_setup.js +1 -0
  38. metadata +4 -8
  39. data/app/services/foreman_rh_cloud/cloud_connector.rb +0 -74
  40. data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext.js +0 -6
  41. data/webpack/__mocks__/foremanReact/common/I18n.js +0 -6
  42. data/webpack/__mocks__/foremanReact/common/helpers.js +0 -14
  43. data/webpack/__mocks__/foremanReact/constants.js +0 -24
  44. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +0 -24
  45. data/webpack/__mocks__/foremanReact/redux/API/index.js +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c2ad7078753ff687e7dfa1cf410efbfbc3f1ad9b8dc74500f9e77bf0c9a7fc9
4
- data.tar.gz: 9eb2679eb38c91fa3c1163915328d91959f7e6408e73bd4fc0c4d7fe98b9a0cf
3
+ metadata.gz: b3d41bcf3b5de41dab8c50751aecf9374fcb0bce44f1e0896011161b25ace53e
4
+ data.tar.gz: 8eb6a78729500fcb4ae06e8a35e452a0ed7a57e8dbc9f1d8ddf46b8c2a341016
5
5
  SHA512:
6
- metadata.gz: 0e4cb92c3a41b257ebf018a988ced92b086aa5cec373214888bb22a3dc43f765a007f42182b1da6a058c97b8633d59e1239efef658c3e0d53744bb9b8fc41125
7
- data.tar.gz: 780195de89dcac76c3774a8b48ae67c7c52ad8b2b4f1029020e1c1e42ec701092f2122ff4caec64de083cf01094ebac40b42cf1d0985d06fdbbc46838331d541
6
+ metadata.gz: d42639d1e4cb269096eb8cabe57a4337ee9c3e04e6339d2fdf8ac79ccf87188d60293820b5c6c61d79d03ecf40b5c042c2da8e81bb70251dda030cc24c74d1e2
7
+ data.tar.gz: c709b8044deff700aa3c541c6c21a9afdec88a449366785251057c64f1d85f73b9080b4391aca99d7564657509e3ee53c03de90f9f6d98dc9239d9de9a29f053
@@ -3,6 +3,7 @@ module Api::V2::RhCloud
3
3
  include ForemanRhCloud::IopSmartProxyAccess
4
4
  layout false
5
5
 
6
+ before_action :set_admin_user, only: [:update]
6
7
  before_action :require_non_iop_smart_proxy, only: [:update]
7
8
 
8
9
  KNOWN_DIRECTIVES = {
@@ -7,7 +7,7 @@ module Api
7
7
  include InventoryUpload::TaskActions
8
8
  include ForemanRhCloud::IopSmartProxyAccess
9
9
 
10
- before_action :require_non_iop_smart_proxy, only: [:enable_cloud_connector]
10
+ before_action :require_non_iop_smart_proxy, only: [:announce_to_sources]
11
11
 
12
12
  api :GET, "/organizations/:organization_id/rh_cloud/report", N_("Download latest report")
13
13
  param :organization_id, Integer, required: true, desc: N_("Set the current organization context for the request")
@@ -70,10 +70,16 @@ module Api
70
70
  render json: { message: error.message }, status: :bad_request
71
71
  end
72
72
 
73
- api :POST, "/rh_cloud/enable_connector", N_("Enable cloud connector")
74
- def enable_cloud_connector
75
- cloud_connector = ForemanRhCloud::CloudConnector.new
76
- render json: cloud_connector.install.to_json
73
+ api :POST, "/rh_cloud/announce_to_sources", N_("Schedule a task to register in Red Hat Sources for cloud connector")
74
+ param :instance_id, String, required: true, desc: N_("RHC instance ID to register with Red Hat Sources")
75
+ def announce_to_sources
76
+ Setting[:rhc_instance_id] = params[:instance_id]
77
+
78
+ task = ForemanTasks.async_task(InsightsCloud::Async::CloudConnectorAnnounceTask, true)
79
+
80
+ render json: {
81
+ task: task,
82
+ }, status: :accepted
77
83
  end
78
84
  end
79
85
  end
@@ -29,7 +29,6 @@ module ForemanInventoryUpload
29
29
 
30
30
  render json: {
31
31
  accounts: accounts,
32
- CloudConnectorStatus: ForemanInventoryUpload::UploadsSettingsController.cloud_connector_status,
33
32
  }, status: :ok
34
33
  end
35
34
 
@@ -1,22 +1,11 @@
1
1
  module ForemanInventoryUpload
2
2
  class UploadsController < ::ApplicationController
3
3
  include InventoryUpload::ReportActions
4
- include ForemanRhCloud::IopSmartProxyAccess
5
-
6
- before_action :require_non_iop_smart_proxy, only: [:enable_cloud_connector]
7
4
 
8
5
  def download_file
9
6
  filename, file = report_file(params[:organization_id])
10
7
 
11
8
  send_file file, disposition: 'attachment', filename: filename
12
9
  end
13
-
14
- def enable_cloud_connector
15
- # Set the autoupload to true, since it's required by the feature.
16
- Setting[:allow_auto_inventory_upload] = true
17
-
18
- cloud_connector = ForemanRhCloud::CloudConnector.new
19
- render json: cloud_connector.install.to_json
20
- end
21
10
  end
22
11
  end
@@ -9,7 +9,6 @@ module ForemanInventoryUpload
9
9
  ipsObfuscationEnabled: Setting[:obfuscate_inventory_ips],
10
10
  excludePackagesEnabled: Setting[:exclude_installed_packages],
11
11
  allowAutoInsightsMismatchDelete: Setting[:allow_auto_insights_mismatch_delete],
12
- CloudConnectorStatus: ForemanInventoryUpload::UploadsSettingsController.cloud_connector_status,
13
12
  lastSyncTask: last_successful_inventory_sync_task,
14
13
  }, status: :ok
15
14
  end
@@ -19,13 +18,6 @@ module ForemanInventoryUpload
19
18
  index
20
19
  end
21
20
 
22
- def self.cloud_connector_status
23
- cloud_connector = ForemanRhCloud::CloudConnector.new
24
- job = cloud_connector&.latest_job
25
- return nil unless job
26
- { id: job.id, task: ForemanTasks::Task.where(:id => job.task_id).first }
27
- end
28
-
29
21
  def last_successful_inventory_sync_task
30
22
  task = ForemanTasks::Task.where(label: 'InventorySync::Async::InventoryFullSync', result: 'success')
31
23
  .reorder('ended_at desc').first
@@ -74,6 +74,9 @@ module ForemanRhCloud
74
74
 
75
75
  def register_rhc_instance
76
76
  raise Foreman::Exception.new('rhc_instance_id is empty, cannot register RHC to the cloud') if Setting[:rhc_instance_id].empty?
77
+
78
+ return :already_registered if rhc_connection_exists?
79
+
77
80
  source_id = satellite_instance_source || create_satellite_instance_source
78
81
 
79
82
  create_response = JSON.parse(
@@ -95,6 +98,22 @@ module ForemanRhCloud
95
98
  @satellite_instance_source = create_response['id']
96
99
  end
97
100
 
101
+ def rhc_connection_exists?
102
+ response = JSON.parse(
103
+ execute_cloud_request(
104
+ method: :get,
105
+ url: rhc_connection_url,
106
+ headers: {
107
+ content_type: :json,
108
+ },
109
+ ssl_client_cert: OpenSSL::X509::Certificate.new(certs[:cert]),
110
+ ssl_client_key: OpenSSL::PKey.read(certs[:key])
111
+ )
112
+ )
113
+
114
+ response['data']&.any?
115
+ end
116
+
98
117
  private
99
118
 
100
119
  def sources_url(path)
@@ -113,6 +132,10 @@ module ForemanRhCloud
113
132
  sources_url('/sources')
114
133
  end
115
134
 
135
+ def rhc_connection_url
136
+ sources_url("/rhc_connections?filter[rhc_id]=#{Setting[:rhc_instance_id]}")
137
+ end
138
+
116
139
  def create_rhc_connections_url
117
140
  sources_url('/rhc_connections')
118
141
  end
data/config/routes.rb CHANGED
@@ -7,8 +7,6 @@ Rails.application.routes.draw do
7
7
  get 'settings', to: 'uploads_settings#index'
8
8
  post 'setting', to: 'uploads_settings#set_advanced_setting'
9
9
 
10
- post 'cloud_connector', to: 'uploads#enable_cloud_connector'
11
-
12
10
  resources :tasks, only: [:create, :show]
13
11
 
14
12
  get 'status', to: 'cloud_status#index'
@@ -72,7 +70,7 @@ Rails.application.routes.draw do
72
70
  end
73
71
 
74
72
  namespace 'rh_cloud' do
75
- post 'enable_connector', to: 'inventory#enable_cloud_connector'
73
+ post 'announce_to_sources', to: 'inventory#announce_to_sources'
76
74
  post 'cloud_request', to: 'cloud_request#update'
77
75
  get 'advisor_engine_config', to: 'advisor_engine_config#show'
78
76
 
@@ -61,6 +61,7 @@ module ForemanRhCloud
61
61
  ForemanRhCloud::Engine.register_scheduled_task(InventorySync::Async::InventoryScheduledSync, '0 0 * * *')
62
62
  ForemanRhCloud::Engine.register_scheduled_task(InsightsCloud::Async::InsightsScheduledSync, '0 0 * * *')
63
63
  ForemanRhCloud::Engine.register_scheduled_task(InsightsCloud::Async::InsightsClientStatusAging, '0 0 * * *')
64
+ ForemanRhCloud::Engine.register_scheduled_task(InsightsCloud::Async::CloudConnectorAnnounceTask, '0 0 * * *')
64
65
  end
65
66
  end
66
67
  rescue ActiveRecord::NoDatabaseError
@@ -80,12 +81,6 @@ module ForemanRhCloud
80
81
  host_action_button: false,
81
82
  provided_inputs: ['playbook_url', 'report_url', 'correlation_id', 'report_interval']
82
83
  )
83
- RemoteExecutionFeature.register(
84
- :ansible_configure_cloud_connector,
85
- N_('Configure Cloud Connector on given hosts'),
86
- :description => N_('Configure Cloud Connector on given hosts'),
87
- :proxy_selector_override => ::RemoteExecutionProxySelector::INTERNAL_PROXY
88
- )
89
84
  end
90
85
 
91
86
  # Ideally this code belongs to an initializer. The problem is that Katello controllers are not initialized completely until after the end of the to_prepare blocks
@@ -31,8 +31,7 @@ module ForemanRhCloud
31
31
  :generate_foreman_rh_cloud,
32
32
  'foreman_inventory_upload/reports': [:generate],
33
33
  'foreman_inventory_upload/tasks': [:create],
34
- 'api/v2/rh_cloud/inventory': [:get_hosts, :remove_hosts, :sync_inventory_status, :download_file, :generate_report, :enable_cloud_connector],
35
- 'foreman_inventory_upload/uploads': [:enable_cloud_connector],
34
+ 'api/v2/rh_cloud/inventory': [:get_hosts, :remove_hosts, :sync_inventory_status, :download_file, :generate_report, :announce_to_sources],
36
35
  'foreman_inventory_upload/uploads_settings': [:set_advanced_setting],
37
36
  'foreman_inventory_upload/missing_hosts': [:remove_hosts],
38
37
  'insights_cloud/settings': [:update],
@@ -123,6 +122,8 @@ module ForemanRhCloud
123
122
  role 'ForemanRhCloud Read Only', read_only_permissions, 'Role granting read-only permissions to view
124
123
  Insights Compliance, Vulnerability, Advisor, and host inventory'
125
124
 
125
+ role 'Cloud Connector', [:dispatch_cloud_requests], 'Role granting permission to dispatch cloud connector requests'
126
+
126
127
  add_permissions_to_default_roles Role::ORG_ADMIN => plugin_permissions,
127
128
  Role::MANAGER => plugin_permissions,
128
129
  Role::SYSTEM_ADMIN => plugin_permissions
@@ -1,3 +1,3 @@
1
1
  module ForemanRhCloud
2
- VERSION = '14.3.0'.freeze
2
+ VERSION = '14.5.0'.freeze
3
3
  end
@@ -1,39 +1,96 @@
1
1
  module InsightsCloud
2
2
  module Async
3
3
  class CloudConnectorAnnounceTask < ::Actions::EntryAction
4
- def self.subscribe
5
- Actions::RemoteExecution::RunHostsJob
6
- end
4
+ include ::Actions::RecurringAction
5
+ include ::ForemanRhCloud::CertAuth
6
+ include ForemanInventoryUpload::Async::DelayedStart
7
7
 
8
- def self.connector_feature_id
9
- @connector_feature_id ||= RemoteExecutionFeature.feature!(ForemanRhCloud::CloudConnector::CLOUD_CONNECTOR_FEATURE).id
10
- end
8
+ def plan(immediate = false)
9
+ if ForemanRhCloud.with_iop_smart_proxy?
10
+ logger.debug('Sources announcement skipped: running in IoP mode')
11
+ return
12
+ end
11
13
 
12
- def plan(job_invocation)
13
- return unless connector_playbook_job?(job_invocation)
14
+ if Setting[:rhc_instance_id].blank?
15
+ logger.debug('Sources announcement skipped: rhc_instance_id is not set')
16
+ return
17
+ end
14
18
 
15
- plan_self
19
+ unless Setting[:allow_auto_inventory_upload]
20
+ logger.debug(
21
+ 'Cloud connector is configured (rhc_instance_id is set) but automatic inventory upload is disabled. ' \
22
+ 'Enable the "Automatic inventory upload" setting for full cloud connector functionality.'
23
+ )
24
+ end
25
+
26
+ if immediate
27
+ plan_self
28
+ else
29
+ after_delay do
30
+ plan_self
31
+ end
32
+ end
16
33
  end
17
34
 
18
- def finalize
35
+ def run
36
+ registered = []
37
+ confirmed_by_remediation = []
38
+ already_registered = []
39
+ skipped = []
40
+ failed = {}
41
+
19
42
  Organization.unscoped.each do |org|
43
+ unless cert_auth_available?(org)
44
+ skipped << org.name
45
+ next
46
+ end
47
+
48
+ if recent_cloud_remediation?(org)
49
+ confirmed_by_remediation << org.name
50
+ next
51
+ end
52
+
20
53
  presence = ForemanRhCloud::CloudPresence.new(org, logger)
21
- presence.announce_to_sources
54
+ result = presence.announce_to_sources
55
+ if result == :already_registered
56
+ already_registered << org.name
57
+ else
58
+ registered << org.name
59
+ end
22
60
  rescue StandardError => ex
23
- logger.warn(ex)
61
+ logger.warn("Failed to announce to Sources for organization #{org.name}: #{ex}")
62
+ logger.debug { ex.backtrace.join("\n") }
63
+ failed[org.name] = ex.message
24
64
  end
25
- end
26
65
 
27
- def rescue_strategy_for_self
28
- Dynflow::Action::Rescue::Skip
66
+ parts = []
67
+ parts << "Registered: #{registered.join(', ')}" if registered.any?
68
+ parts << "Already registered: #{already_registered.join(', ')}" if already_registered.any?
69
+ parts << "Already registered (recent cloud remediation): #{confirmed_by_remediation.join(', ')}" if confirmed_by_remediation.any?
70
+ parts << "Skipped (no manifest): #{skipped.join(', ')}" if skipped.any?
71
+ if failed.any?
72
+ failed_details = failed.map { |name, msg| "#{name}: #{msg}" }.join('; ')
73
+ parts << "Failed: #{failed_details}"
74
+ end
75
+ output[:status] = parts.join('. ')
76
+
77
+ error!("Sources announcement failed for: #{failed.keys.join(', ')}") if failed.any?
29
78
  end
30
79
 
31
- def connector_playbook_job?(job_invocation)
32
- job_invocation&.remote_execution_feature_id == connector_feature_id
80
+ def recent_cloud_remediation?(org)
81
+ feature = RemoteExecutionFeature.find_by(label: 'rh_cloud_connector_run_playbook')
82
+ return false unless feature
83
+
84
+ JobInvocation.where(remote_execution_feature_id: feature.id)
85
+ .joins(:task)
86
+ .joins(targeting: :hosts)
87
+ .where(hosts: { organization_id: org.id })
88
+ .where('foreman_tasks_tasks.started_at > ?', 24.hours.ago)
89
+ .exists?
33
90
  end
34
91
 
35
- def connector_feature_id
36
- self.class.connector_feature_id
92
+ def rescue_strategy_for_self
93
+ Dynflow::Action::Rescue::Skip
37
94
  end
38
95
 
39
96
  def logger
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreman_rh_cloud",
3
- "version": "14.3.0",
3
+ "version": "14.5.0",
4
4
  "description": "Inventory Upload =============",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -35,20 +35,22 @@ module InventoryUpload::Api
35
35
  assert_equal test_task.id.to_s, actual_task['id']
36
36
  end
37
37
 
38
- test 'Starts cloud connector configuration job' do
39
- test_job = FactoryBot.create(:job_invocation)
38
+ test 'Triggers Sources announcement task' do
39
+ test_task = FactoryBot.create(:some_task)
40
40
 
41
- ForemanRhCloud::CloudConnector.any_instance
42
- .expects(:install)
43
- .returns(test_job)
41
+ ForemanTasks.expects(:async_task)
42
+ .with(InsightsCloud::Async::CloudConnectorAnnounceTask, true)
43
+ .returns(test_task)
44
44
 
45
- post :enable_cloud_connector
45
+ post :announce_to_sources, params: { instance_id: 'test-instance-id' }
46
46
 
47
47
  assert_response :success
48
48
 
49
- actual_job = @response.parsed_body
49
+ assert_equal 'test-instance-id', Setting[:rhc_instance_id]
50
50
 
51
- assert_equal test_job.id, actual_job['id']
51
+ actual_task = @response.parsed_body['task']
52
+ assert_not_nil actual_task
53
+ assert_equal test_task.id.to_s, actual_task['id']
52
54
  end
53
55
  end
54
56
  end
@@ -1,125 +1,135 @@
1
- require 'json'
2
1
  require 'test_plugin_helper'
3
2
  require 'foreman_tasks/test_helpers'
4
- require "#{ForemanTasks::Engine.root}/test/support/dummy_dynflow_action"
5
3
 
6
4
  class CloudConnectorAnnounceTaskTest < ActiveSupport::TestCase
7
5
  include Dynflow::Testing::Factories
8
6
 
9
7
  setup do
10
- RemoteExecutionFeature.register(
11
- :ansible_configure_cloud_connector,
12
- N_('Configure Cloud Connector on given hosts'),
13
- :description => N_('Configure Cloud Connector on given hosts'),
14
- :proxy_selector_override => ::RemoteExecutionProxySelector::INTERNAL_PROXY
15
- )
8
+ InsightsCloud::Async::CloudConnectorAnnounceTask.any_instance
9
+ .stubs(:recent_cloud_remediation?).returns(false)
10
+ end
11
+
12
+ teardown do
13
+ ForemanRhCloud.unstub(:with_iop_smart_proxy?)
14
+ end
15
+
16
+ test 'announces to sources for all organizations when rhc_instance_id is set' do
17
+ Setting[:rhc_instance_id] = 'test-rhc-id'
18
+
19
+ InsightsCloud::Async::CloudConnectorAnnounceTask.any_instance
20
+ .stubs(:cert_auth_available?).returns(true)
16
21
 
17
- @job_invocation = generate_job_invocation(:ansible_configure_cloud_connector)
22
+ ForemanRhCloud::CloudPresence.any_instance
23
+ .expects(:announce_to_sources)
24
+ .times(Organization.unscoped.count)
18
25
 
19
- # reset connector feature ID cache
20
- InsightsCloud::Async::CloudConnectorAnnounceTask.instance_variable_set(:@connector_feature_id, nil)
26
+ action = create_and_plan_action(InsightsCloud::Async::CloudConnectorAnnounceTask)
27
+ action = run_action(action)
28
+
29
+ status = action.output[:status].to_s
30
+ assert_match(/Registered:|Already registered:/, status)
31
+ refute_match(/Skipped/, status)
32
+ refute_match(/Failed/, status)
21
33
  end
22
34
 
23
- test 'It executes cloud presence announcer' do
24
- ForemanRhCloud::CloudPresence.any_instance.expects(:announce_to_sources).times(Organization.unscoped.count)
35
+ test 'skips when rhc_instance_id is not set' do
36
+ Setting[:rhc_instance_id] = nil
37
+
38
+ action = create_and_plan_action(InsightsCloud::Async::CloudConnectorAnnounceTask)
25
39
 
26
- action = create_and_plan_action(InsightsCloud::Async::CloudConnectorAnnounceTask, @job_invocation)
27
- finalize_action(action)
40
+ assert_empty action.execution_plan.planned_run_steps
28
41
  end
29
42
 
30
- private
31
-
32
- def generate_job_invocation(feature_name)
33
- job_template = FactoryBot.build(
34
- :job_template,
35
- :template => 'BLEH'
36
- )
37
- feature = RemoteExecutionFeature.feature!(feature_name).id
38
-
39
- job_invocation = FactoryBot.create(
40
- :job_invocation,
41
- remote_execution_feature_id: feature,
42
- task_id: FactoryBot.create(:dynflow_task).id
43
- )
44
-
45
- job_template.template_inputs << playbook_url_input = FactoryBot.build(:template_input,
46
- :name => 'playbook_url',
47
- :input_type => 'user',
48
- :required => true)
49
- job_template.template_inputs << report_url_input = FactoryBot.build(:template_input,
50
- :name => 'report_url',
51
- :input_type => 'user',
52
- :required => true)
53
- job_template.template_inputs << correlation_id_input = FactoryBot.build(:template_input,
54
- :name => 'correlation_id',
55
- :input_type => 'user',
56
- :required => true)
57
- job_template.template_inputs << report_interval_input = FactoryBot.build(:template_input,
58
- :name => 'report_interval',
59
- :input_type => 'user',
60
- :required => true)
61
-
62
- template_invocation = FactoryBot.build(:template_invocation,
63
- :template => job_template,
64
- :job_invocation => job_invocation)
65
-
66
- template_invocation.input_values << FactoryBot.create(
67
- :template_invocation_input_value,
68
- :template_invocation => template_invocation,
69
- :template_input => playbook_url_input,
70
- :value => 'http://example.com/TEST_PLAYBOOK'
71
- )
72
- template_invocation.input_values << FactoryBot.create(
73
- :template_invocation_input_value,
74
- :template_invocation => template_invocation,
75
- :template_input => report_url_input,
76
- :value => 'http://example.com/TEST_REPORT'
77
- )
78
- template_invocation.input_values << FactoryBot.create(
79
- :template_invocation_input_value,
80
- :template_invocation => template_invocation,
81
- :template_input => correlation_id_input,
82
- :value => 'TEST_CORRELATION'
83
- )
84
- template_invocation.input_values << FactoryBot.create(
85
- :template_invocation_input_value,
86
- :template_invocation => template_invocation,
87
- :template_input => report_interval_input,
88
- :value => '1'
89
- )
90
-
91
- @host1 = FactoryBot.create(:host, :with_insights_hits, name: 'host1')
92
- @host1.insights.uuid = 'TEST_UUID1'
93
- @host1.insights.save!
94
- @host2 = FactoryBot.create(:host, :with_insights_hits, name: 'host2')
95
- @host2.insights.uuid = 'TEST_UUID2'
96
- @host2.insights.save!
97
-
98
- targeting = FactoryBot.create(:targeting, hosts: [@host1, @host2])
99
- job_invocation.targeting = targeting
100
- job_invocation.save!
101
-
102
- job_invocation.template_invocations << FactoryBot.create(
103
- :template_invocation,
104
- run_host_job_task: FactoryBot.create(:dynflow_task),
105
- host_id: @host1.id
106
- )
107
- job_invocation.template_invocations << FactoryBot.create(
108
- :template_invocation,
109
- run_host_job_task: FactoryBot.create(:dynflow_task),
110
- host_id: @host2.id
111
- )
112
-
113
- fake_output = (1..5).map do |i|
114
- { 'timestamp' => (Time.now - (5 - i)).to_f, 'output' => "#{i}\n" }
115
- end
116
- Support::DummyDynflowAction.any_instance.stubs(:live_output).returns(fake_output)
117
- Support::DummyDynflowAction.any_instance.stubs(:exit_status).returns(0)
43
+ test 'skips when rhc_instance_id is empty string' do
44
+ Setting[:rhc_instance_id] = ''
45
+
46
+ action = create_and_plan_action(InsightsCloud::Async::CloudConnectorAnnounceTask)
47
+
48
+ assert_empty action.execution_plan.planned_run_steps
49
+ end
50
+
51
+ test 'skips when in IoP mode' do
52
+ Setting[:rhc_instance_id] = 'test-rhc-id'
53
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
54
+
55
+ action = create_and_plan_action(InsightsCloud::Async::CloudConnectorAnnounceTask)
56
+
57
+ assert_empty action.execution_plan.planned_run_steps
58
+ end
59
+
60
+ test 'skips organizations without a manifest' do
61
+ Setting[:rhc_instance_id] = 'test-rhc-id'
62
+
63
+ InsightsCloud::Async::CloudConnectorAnnounceTask.any_instance
64
+ .stubs(:cert_auth_available?).returns(false)
65
+
66
+ ForemanRhCloud::CloudPresence.any_instance
67
+ .expects(:announce_to_sources)
68
+ .never
118
69
 
119
- job_invocation
70
+ action = create_and_plan_action(InsightsCloud::Async::CloudConnectorAnnounceTask)
71
+ action = run_action(action)
72
+
73
+ status = action.output[:status].to_s
74
+ assert_match(/Skipped \(no manifest\):/, status)
75
+ refute_match(/Registered/, status)
76
+ refute_match(/Failed/, status)
77
+ end
78
+
79
+ test 'still runs when allow_auto_inventory_upload is disabled' do
80
+ Setting[:rhc_instance_id] = 'test-rhc-id'
81
+ Setting[:allow_auto_inventory_upload] = false
82
+
83
+ InsightsCloud::Async::CloudConnectorAnnounceTask.any_instance
84
+ .stubs(:cert_auth_available?).returns(true)
85
+
86
+ ForemanRhCloud::CloudPresence.any_instance
87
+ .expects(:announce_to_sources)
88
+ .times(Organization.unscoped.count)
89
+
90
+ action = create_and_plan_action(InsightsCloud::Async::CloudConnectorAnnounceTask)
91
+ action = run_action(action)
92
+
93
+ assert_match(/Registered:|Already registered:/, action.output[:status].to_s)
94
+ end
95
+
96
+ test 'skips orgs with recent cloud remediation jobs' do
97
+ Setting[:rhc_instance_id] = 'test-rhc-id'
98
+
99
+ InsightsCloud::Async::CloudConnectorAnnounceTask.any_instance
100
+ .stubs(:cert_auth_available?).returns(true)
101
+ InsightsCloud::Async::CloudConnectorAnnounceTask.any_instance
102
+ .stubs(:recent_cloud_remediation?).returns(true)
103
+
104
+ ForemanRhCloud::CloudPresence.any_instance
105
+ .expects(:announce_to_sources)
106
+ .never
107
+
108
+ action = create_and_plan_action(InsightsCloud::Async::CloudConnectorAnnounceTask)
109
+ action = run_action(action)
110
+
111
+ status = action.output[:status].to_s
112
+ assert_match(/Already registered \(recent cloud remediation\):/, status)
113
+ refute_match(/\bRegistered:/, status)
120
114
  end
121
115
 
122
- def read_jsonl(jsonl)
123
- jsonl.lines.map { |l| JSON.parse(l) }
116
+ test 'continues processing other orgs when one fails and task errors' do
117
+ Setting[:rhc_instance_id] = 'test-rhc-id'
118
+
119
+ InsightsCloud::Async::CloudConnectorAnnounceTask.any_instance
120
+ .stubs(:cert_auth_available?).returns(true)
121
+
122
+ call_count = 0
123
+ ForemanRhCloud::CloudPresence.any_instance.stubs(:announce_to_sources).with do
124
+ call_count += 1
125
+ raise(StandardError.new('API error')) if call_count == 1
126
+ true
127
+ end
128
+
129
+ action = create_and_plan_action(InsightsCloud::Async::CloudConnectorAnnounceTask)
130
+
131
+ error = assert_raises(StandardError) { run_action(action) }
132
+ assert_match(/Sources announcement failed for:/, error.message)
133
+ assert_equal Organization.unscoped.count, call_count
124
134
  end
125
135
  end
@@ -79,6 +79,7 @@ const MinimalInventoryDropdown = ({ setChosenValue }) => {
79
79
  isFullWidth
80
80
  onClick={onToggleClick}
81
81
  isExpanded={isOpen}
82
+ ouiaId="inventory-dropdown-toggle"
82
83
  >
83
84
  {dropdownValues[currentDropdownValue].title}
84
85
  </MenuToggle>
@@ -59,6 +59,7 @@ const PageTitle = () => {
59
59
  variant="plain"
60
60
  onClick={() => setIsDropdownOpen(prev => !prev)}
61
61
  isExpanded={isDropdownOpen}
62
+ ouiaId="title-dropdown-toggle"
62
63
  >
63
64
  <EllipsisVIcon />
64
65
  </MenuToggle>