foreman_rh_cloud 14.4.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.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/rh_cloud/cloud_request_controller.rb +1 -0
- data/app/controllers/api/v2/rh_cloud/inventory_controller.rb +11 -5
- data/app/controllers/foreman_inventory_upload/accounts_controller.rb +0 -1
- data/app/controllers/foreman_inventory_upload/uploads_controller.rb +0 -11
- data/app/controllers/foreman_inventory_upload/uploads_settings_controller.rb +0 -8
- data/app/services/foreman_rh_cloud/cloud_presence.rb +23 -0
- data/config/routes.rb +1 -3
- data/lib/foreman_rh_cloud/engine.rb +1 -6
- data/lib/foreman_rh_cloud/plugin.rb +3 -2
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/lib/insights_cloud/async/cloud_connector_announce_task.rb +76 -19
- data/package.json +1 -1
- data/test/controllers/inventory_upload/api/inventory_controller_test.rb +10 -8
- data/test/jobs/cloud_connector_announce_task_test.rb +116 -106
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/integrations.test.js +4 -12
- data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTableActions.test.js +2 -2
- data/webpack/InsightsCloudSync/__tests__/InsightsCloudSyncActions.test.js +7 -5
- data/webpack/InsightsVulnerabilityActionsBar/__tests__/InsightsVulnerabilityActionsBar.test.js +1 -5
- data/webpack/test_setup.js +1 -0
- metadata +2 -8
- data/app/services/foreman_rh_cloud/cloud_connector.rb +0 -74
- data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext.js +0 -6
- data/webpack/__mocks__/foremanReact/common/I18n.js +0 -6
- data/webpack/__mocks__/foremanReact/common/helpers.js +0 -14
- data/webpack/__mocks__/foremanReact/constants.js +0 -24
- data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +0 -24
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b3d41bcf3b5de41dab8c50751aecf9374fcb0bce44f1e0896011161b25ace53e
|
|
4
|
+
data.tar.gz: 8eb6a78729500fcb4ae06e8a35e452a0ed7a57e8dbc9f1d8ddf46b8c2a341016
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d42639d1e4cb269096eb8cabe57a4337ee9c3e04e6339d2fdf8ac79ccf87188d60293820b5c6c61d79d03ecf40b5c042c2da8e81bb70251dda030cc24c74d1e2
|
|
7
|
+
data.tar.gz: c709b8044deff700aa3c541c6c21a9afdec88a449366785251057c64f1d85f73b9080b4391aca99d7564657509e3ee53c03de90f9f6d98dc9239d9de9a29f053
|
|
@@ -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: [:
|
|
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/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
@@ -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 '
|
|
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, :
|
|
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,39 +1,96 @@
|
|
|
1
1
|
module InsightsCloud
|
|
2
2
|
module Async
|
|
3
3
|
class CloudConnectorAnnounceTask < ::Actions::EntryAction
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
include ::Actions::RecurringAction
|
|
5
|
+
include ::ForemanRhCloud::CertAuth
|
|
6
|
+
include ForemanInventoryUpload::Async::DelayedStart
|
|
7
7
|
|
|
8
|
-
def
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
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
|
|
32
|
-
|
|
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
|
|
36
|
-
|
|
92
|
+
def rescue_strategy_for_self
|
|
93
|
+
Dynflow::Action::Rescue::Skip
|
|
37
94
|
end
|
|
38
95
|
|
|
39
96
|
def logger
|
data/package.json
CHANGED
|
@@ -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 '
|
|
39
|
-
|
|
38
|
+
test 'Triggers Sources announcement task' do
|
|
39
|
+
test_task = FactoryBot.create(:some_task)
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
.
|
|
43
|
-
.returns(
|
|
41
|
+
ForemanTasks.expects(:async_task)
|
|
42
|
+
.with(InsightsCloud::Async::CloudConnectorAnnounceTask, true)
|
|
43
|
+
.returns(test_task)
|
|
44
44
|
|
|
45
|
-
post :
|
|
45
|
+
post :announce_to_sources, params: { instance_id: 'test-instance-id' }
|
|
46
46
|
|
|
47
47
|
assert_response :success
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
assert_equal 'test-instance-id', Setting[:rhc_instance_id]
|
|
50
50
|
|
|
51
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
22
|
+
ForemanRhCloud::CloudPresence.any_instance
|
|
23
|
+
.expects(:announce_to_sources)
|
|
24
|
+
.times(Organization.unscoped.count)
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
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 '
|
|
24
|
-
|
|
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
|
|
27
|
-
finalize_action(action)
|
|
40
|
+
assert_empty action.execution_plan.planned_run_steps
|
|
28
41
|
end
|
|
29
42
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
|
@@ -3,24 +3,16 @@ import { render, screen, fireEvent } from '@testing-library/react';
|
|
|
3
3
|
import { Provider } from 'react-redux';
|
|
4
4
|
import configureMockStore from 'redux-mock-store';
|
|
5
5
|
import thunk from 'redux-thunk';
|
|
6
|
-
import * as API from 'foremanReact/redux/API';
|
|
7
6
|
import ConnectedSyncButton from '../index';
|
|
8
|
-
import { successResponse } from './SyncButtonFixtures';
|
|
9
|
-
import { INVENTORY_SYNC } from '../SyncButtonConstants';
|
|
10
|
-
|
|
11
|
-
jest.spyOn(API, 'post');
|
|
12
7
|
|
|
13
8
|
const mockStore = configureMockStore([thunk]);
|
|
14
9
|
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
});
|
|
13
|
+
|
|
15
14
|
describe('SyncButton integration test', () => {
|
|
16
15
|
it('dispatches sync action when button is clicked', () => {
|
|
17
|
-
API.post.mockImplementation(({ handleSuccess, key, ...action }) => {
|
|
18
|
-
if (key === INVENTORY_SYNC && handleSuccess) {
|
|
19
|
-
handleSuccess(successResponse);
|
|
20
|
-
}
|
|
21
|
-
return { type: 'API_POST', ...action };
|
|
22
|
-
});
|
|
23
|
-
|
|
24
16
|
const store = mockStore({
|
|
25
17
|
API: {},
|
|
26
18
|
});
|
data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTableActions.test.js
CHANGED
|
@@ -117,10 +117,10 @@ describe('InsightsTable actions', () => {
|
|
|
117
117
|
expect(push).toHaveBeenCalled();
|
|
118
118
|
|
|
119
119
|
const apiAction = dispatch.mock.calls.find(
|
|
120
|
-
call => call[0] && call[0].key === INSIGHTS_HITS_API_KEY
|
|
120
|
+
call => call[0] && call[0].payload?.key === INSIGHTS_HITS_API_KEY
|
|
121
121
|
);
|
|
122
122
|
expect(apiAction).toBeTruthy();
|
|
123
|
-
const getArg = apiAction[0];
|
|
123
|
+
const getArg = apiAction[0].payload;
|
|
124
124
|
expect(getArg.url).toBe(INSIGHTS_HITS_PATH);
|
|
125
125
|
expect(getArg.params.page).toBe(2);
|
|
126
126
|
expect(getArg.params.per_page).toBe(7);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { API_OPERATIONS } from 'foremanReact/redux/API';
|
|
1
2
|
import { syncInsights } from '../InsightsCloudSyncActions';
|
|
2
3
|
import { INSIGHTS_CLOUD_SYNC } from '../InsightsCloudSyncConstants';
|
|
3
4
|
|
|
@@ -17,17 +18,18 @@ describe('InsightsCloudSync actions', () => {
|
|
|
17
18
|
|
|
18
19
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
|
19
20
|
const dispatched = dispatch.mock.calls[0][0];
|
|
20
|
-
expect(dispatched.
|
|
21
|
-
expect(dispatched.
|
|
22
|
-
expect(
|
|
23
|
-
expect(typeof dispatched.
|
|
21
|
+
expect(dispatched.type).toBe(API_OPERATIONS.POST);
|
|
22
|
+
expect(dispatched.payload.key).toBe(INSIGHTS_CLOUD_SYNC);
|
|
23
|
+
expect(dispatched.payload.url).toBe('/insights_cloud/tasks');
|
|
24
|
+
expect(typeof dispatched.payload.handleSuccess).toBe('function');
|
|
25
|
+
expect(typeof dispatched.payload.errorToast).toBe('function');
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
it('errorToast returns failure message with error details', () => {
|
|
27
29
|
syncInsights(jest.fn(), '')(dispatch);
|
|
28
30
|
const dispatched = dispatch.mock.calls[0][0];
|
|
29
31
|
|
|
30
|
-
const result = dispatched.errorToast('some error');
|
|
32
|
+
const result = dispatched.payload.errorToast('some error');
|
|
31
33
|
expect(result).toContain('some error');
|
|
32
34
|
});
|
|
33
35
|
});
|
data/webpack/InsightsVulnerabilityActionsBar/__tests__/InsightsVulnerabilityActionsBar.test.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import '@testing-library/jest-dom';
|
|
4
|
-
import * as ForemanContext from 'foremanReact/Root/Context/ForemanContext';
|
|
5
4
|
import { ForemanHostsIndexActionsBarContext } from 'foremanReact/components/HostsIndex';
|
|
6
5
|
import * as ConfigHooks from '../../common/Hooks/ConfigHooks';
|
|
7
6
|
import InsightsVulnerabilityActionsBar from '../index';
|
|
8
7
|
|
|
9
8
|
jest.mock('../../common/Hooks/ConfigHooks');
|
|
10
|
-
jest.mock('foremanReact/Root/Context/ForemanContext');
|
|
11
9
|
jest.mock('../InsightsVulnerabilityActionsBarActions');
|
|
12
10
|
|
|
13
11
|
const mockContextValue = {
|
|
@@ -16,11 +14,9 @@ const mockContextValue = {
|
|
|
16
14
|
setMenuOpen: jest.fn(),
|
|
17
15
|
};
|
|
18
16
|
|
|
19
|
-
const renderComponent = (contextOverrides = {}
|
|
17
|
+
const renderComponent = (contextOverrides = {}) => {
|
|
20
18
|
const contextValue = { ...mockContextValue, ...contextOverrides };
|
|
21
19
|
|
|
22
|
-
ForemanContext.useForemanOrganization.mockReturnValue({ id: orgId });
|
|
23
|
-
|
|
24
20
|
return render(
|
|
25
21
|
<ForemanHostsIndexActionsBarContext.Provider value={contextValue}>
|
|
26
22
|
<InsightsVulnerabilityActionsBar />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'foremanJSTestSetup';
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foreman_rh_cloud
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 14.
|
|
4
|
+
version: 14.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Foreman Red Hat Cloud team
|
|
@@ -107,7 +107,6 @@ files:
|
|
|
107
107
|
- app/overrides/layouts/base/styles.html.erb.deface
|
|
108
108
|
- app/services/foreman_rh_cloud/branch_info.rb
|
|
109
109
|
- app/services/foreman_rh_cloud/cert_auth.rb
|
|
110
|
-
- app/services/foreman_rh_cloud/cloud_connector.rb
|
|
111
110
|
- app/services/foreman_rh_cloud/cloud_ping_service.rb
|
|
112
111
|
- app/services/foreman_rh_cloud/cloud_presence.rb
|
|
113
112
|
- app/services/foreman_rh_cloud/cloud_request.rb
|
|
@@ -478,20 +477,14 @@ files:
|
|
|
478
477
|
- webpack/InsightsVulnerabilityHostIndexExtensions/__tests__/CVECountCell.test.js
|
|
479
478
|
- webpack/IopPathwayDetails/IopPathwayDetails.js
|
|
480
479
|
- webpack/IopRecommendationDetails/IopRecommendationDetails.js
|
|
481
|
-
- webpack/__mocks__/foremanReact/Root/Context/ForemanContext.js
|
|
482
|
-
- webpack/__mocks__/foremanReact/common/I18n.js
|
|
483
480
|
- webpack/__mocks__/foremanReact/common/MountingService.js
|
|
484
|
-
- webpack/__mocks__/foremanReact/common/helpers.js
|
|
485
481
|
- webpack/__mocks__/foremanReact/components/ConfirmModal/index.js
|
|
486
482
|
- webpack/__mocks__/foremanReact/components/Head.js
|
|
487
483
|
- webpack/__mocks__/foremanReact/components/HostsIndex/index.js
|
|
488
484
|
- webpack/__mocks__/foremanReact/components/Layout/LayoutConstants.js
|
|
489
485
|
- webpack/__mocks__/foremanReact/components/ToastsList/index.js
|
|
490
486
|
- webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.js
|
|
491
|
-
- webpack/__mocks__/foremanReact/constants.js
|
|
492
487
|
- webpack/__mocks__/foremanReact/redux.js
|
|
493
|
-
- webpack/__mocks__/foremanReact/redux/API/APISelectors.js
|
|
494
|
-
- webpack/__mocks__/foremanReact/redux/API/index.js
|
|
495
488
|
- webpack/__mocks__/foremanReact/redux/middlewares/IntervalMiddleware.js
|
|
496
489
|
- webpack/__mocks__/foremanReact/routes/RouterSelector.js
|
|
497
490
|
- webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js
|
|
@@ -514,6 +507,7 @@ files:
|
|
|
514
507
|
- webpack/common/table/helpers.js
|
|
515
508
|
- webpack/global_index.js
|
|
516
509
|
- webpack/index.js
|
|
510
|
+
- webpack/test_setup.js
|
|
517
511
|
homepage: https://github.com/theforeman/foreman_rh_cloud
|
|
518
512
|
licenses:
|
|
519
513
|
- GPL-3.0
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
require 'securerandom'
|
|
2
|
-
|
|
3
|
-
module ForemanRhCloud
|
|
4
|
-
class CloudConnector
|
|
5
|
-
CLOUD_CONNECTOR_USER = 'cloud_connector_user'.freeze
|
|
6
|
-
CLOUD_CONNECTOR_TOKEN_NAME = 'Cloud connector access token'.freeze
|
|
7
|
-
CLOUD_CONNECTOR_FEATURE = 'ansible_configure_cloud_connector'.freeze
|
|
8
|
-
|
|
9
|
-
def install
|
|
10
|
-
user = service_user
|
|
11
|
-
token_value = personal_access_token(user)
|
|
12
|
-
target_host = foreman_host
|
|
13
|
-
composer = nil
|
|
14
|
-
|
|
15
|
-
input = {
|
|
16
|
-
:satellite_cloud_connector_user => service_user.login,
|
|
17
|
-
:satellite_cloud_connector_password => token_value,
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (http_proxy = ForemanRhCloud.proxy_setting)
|
|
21
|
-
input[:satellite_cloud_connector_http_proxy] = http_proxy
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
Taxonomy.as_taxonomy(target_host.organization, target_host.location) do
|
|
25
|
-
composer = ::JobInvocationComposer.for_feature(
|
|
26
|
-
CLOUD_CONNECTOR_FEATURE,
|
|
27
|
-
[target_host.id],
|
|
28
|
-
input
|
|
29
|
-
)
|
|
30
|
-
composer.trigger!
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
composer.job_invocation
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def latest_job
|
|
37
|
-
feature_id = RemoteExecutionFeature.find_by_label(CLOUD_CONNECTOR_FEATURE)&.id
|
|
38
|
-
return nil unless feature_id
|
|
39
|
-
JobInvocation.where(:remote_execution_feature_id => feature_id).includes(:task).reorder('foreman_tasks_tasks.started_at DESC').first
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def personal_access_token(user)
|
|
43
|
-
access_token = PersonalAccessToken.find_by_name(CLOUD_CONNECTOR_TOKEN_NAME)
|
|
44
|
-
|
|
45
|
-
access_token&.destroy! # destroy the old token if exists
|
|
46
|
-
|
|
47
|
-
personal_access_token = PersonalAccessToken.new(:name => CLOUD_CONNECTOR_TOKEN_NAME, :user => user)
|
|
48
|
-
token_value = personal_access_token.generate_token
|
|
49
|
-
personal_access_token.save!
|
|
50
|
-
|
|
51
|
-
token_value
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def service_user
|
|
55
|
-
user = User.find_by_login(CLOUD_CONNECTOR_USER)
|
|
56
|
-
|
|
57
|
-
if user.nil?
|
|
58
|
-
user = User.create!(
|
|
59
|
-
:login => CLOUD_CONNECTOR_USER,
|
|
60
|
-
:password => SecureRandom.base64(255),
|
|
61
|
-
:description => "This is a service user used by cloud connector to talk to the Satellite API",
|
|
62
|
-
:auth_source => AuthSourceInternal.first,
|
|
63
|
-
:admin => true
|
|
64
|
-
)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
user
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def foreman_host
|
|
71
|
-
ForemanRhCloud.foreman_host
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import camelCase from 'lodash/camelCase';
|
|
2
|
-
|
|
3
|
-
export const getURIQuery = jest.fn(() => ({}));
|
|
4
|
-
|
|
5
|
-
export const noop = Function.prototype;
|
|
6
|
-
|
|
7
|
-
export const propsToCamelCase = ob => {
|
|
8
|
-
if (typeof ob !== 'object' || ob === null) return ob;
|
|
9
|
-
|
|
10
|
-
return Object.keys(ob).reduce((memo, key) => {
|
|
11
|
-
memo[camelCase(key)] = ob[key];
|
|
12
|
-
return memo;
|
|
13
|
-
}, {});
|
|
14
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export const STATUS = {
|
|
2
|
-
PENDING: 'PENDING',
|
|
3
|
-
RESOLVED: 'RESOLVED',
|
|
4
|
-
ERROR: 'ERROR',
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export const getControllerSearchProps = (
|
|
8
|
-
controller,
|
|
9
|
-
id = 'searchBar',
|
|
10
|
-
canCreateBookmarks = true
|
|
11
|
-
) => ({
|
|
12
|
-
controller,
|
|
13
|
-
autocomplete: {
|
|
14
|
-
id,
|
|
15
|
-
searchQuery: '',
|
|
16
|
-
url: `${controller}/auto_complete_search`,
|
|
17
|
-
useKeyShortcuts: true,
|
|
18
|
-
},
|
|
19
|
-
bookmarks: {
|
|
20
|
-
url: '/api/bookmarks',
|
|
21
|
-
canCreateBookmarks,
|
|
22
|
-
documentationUrl: `4.1.5Searching`,
|
|
23
|
-
},
|
|
24
|
-
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { STATUS } from '../../constants';
|
|
2
|
-
|
|
3
|
-
export const selectAPI = state => state.API || {};
|
|
4
|
-
|
|
5
|
-
export const selectAPIByKey = (state, key) => selectAPI(state)[key] || {};
|
|
6
|
-
|
|
7
|
-
export const selectAPIStatus = (state, key) =>
|
|
8
|
-
selectAPIByKey(state, key).status;
|
|
9
|
-
|
|
10
|
-
export const selectAPIPayload = (state, key) =>
|
|
11
|
-
selectAPIByKey(state, key).payload || {};
|
|
12
|
-
|
|
13
|
-
export const selectAPIResponse = (state, key) =>
|
|
14
|
-
selectAPIByKey(state, key).response || {};
|
|
15
|
-
|
|
16
|
-
export const selectAPIError = (state, key) =>
|
|
17
|
-
selectAPIStatus(state, key) === STATUS.ERROR
|
|
18
|
-
? selectAPIResponse(state, key)
|
|
19
|
-
: null;
|
|
20
|
-
|
|
21
|
-
export const selectAPIErrorMessage = (state, key) => {
|
|
22
|
-
const error = selectAPIError(state, key);
|
|
23
|
-
return error && error.message;
|
|
24
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export const get = data => ({ type: 'get-some-type', ...data });
|
|
2
|
-
export const put = data => ({ type: 'put-some-type', ...data });
|
|
3
|
-
export const post = data => ({ type: 'post-some-type', ...data });
|
|
4
|
-
export const patch = data => ({ type: 'patch-some-type', ...data });
|
|
5
|
-
|
|
6
|
-
export const API = {
|
|
7
|
-
get: jest.fn(),
|
|
8
|
-
put: jest.fn(),
|
|
9
|
-
post: jest.fn(),
|
|
10
|
-
delete: jest.fn(),
|
|
11
|
-
patch: jest.fn(),
|
|
12
|
-
};
|