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.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/rh_cloud/cloud_request_controller.rb +83 -0
- data/app/controllers/foreman_inventory_upload/uploads_controller.rb +7 -0
- data/app/models/setting/rh_cloud.rb +2 -1
- data/app/services/foreman_rh_cloud/cloud_presence.rb +124 -0
- data/app/services/foreman_rh_cloud/cloud_request.rb +8 -1
- data/app/services/foreman_rh_cloud/hit_remediations_retriever.rb +67 -0
- data/app/services/foreman_rh_cloud/remediations_retriever.rb +16 -45
- data/app/services/foreman_rh_cloud/template_renderer_helper.rb +13 -1
- data/app/services/foreman_rh_cloud/url_remediations_retriever.rb +37 -0
- data/app/views/job_templates/rh_cloud_download_playbook.erb +26 -0
- data/config/routes.rb +2 -0
- data/lib/foreman_rh_cloud/engine.rb +13 -2
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/lib/foreman_rh_cloud.rb +4 -0
- data/lib/insights_cloud/async/connector_playbook_execution_reporter_task.rb +193 -0
- data/lib/insights_cloud/generators/playbook_progress_generator.rb +49 -0
- data/lib/tasks/insights.rake +13 -0
- data/package.json +1 -1
- data/test/controllers/insights_cloud/api/cloud_request_controller_test.rb +78 -0
- data/test/jobs/connector_playbook_execution_reporter_task_test.rb +207 -0
- data/test/unit/playbook_progress_generator_test.rb +75 -0
- data/test/unit/services/foreman_rh_cloud/{remediations_retriever_test.rb → hit_remediations_retriever_test.rb} +3 -3
- data/test/unit/services/foreman_rh_cloud/url_remediations_retriever_test.rb +27 -0
- metadata +19 -4
@@ -0,0 +1,193 @@
|
|
1
|
+
module InsightsCloud
|
2
|
+
module Async
|
3
|
+
class ConnectorPlaybookExecutionReporterTask < ::Actions::EntryAction
|
4
|
+
include Dynflow::Action::Polling
|
5
|
+
include ForemanRhCloud::CloudAuth
|
6
|
+
|
7
|
+
def self.subscribe
|
8
|
+
Actions::RemoteExecution::RunHostsJob
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.connector_feature_id
|
12
|
+
@connector_feature_id ||= RemoteExecutionFeature.feature!(:rh_cloud_connector_run_playbook).id
|
13
|
+
end
|
14
|
+
|
15
|
+
def plan(job_invocation)
|
16
|
+
return unless connector_playbook_job?(job_invocation)
|
17
|
+
|
18
|
+
@job_invocation = job_invocation
|
19
|
+
|
20
|
+
invocation_inputs = invocation_inputs(job_invocation)
|
21
|
+
report_url = invocation_inputs['report_url']
|
22
|
+
report_interval = [invocation_inputs['report_interval'].to_i, 5].max
|
23
|
+
correlation_id = invocation_inputs['correlation_id']
|
24
|
+
|
25
|
+
plan_self(
|
26
|
+
report_url: report_url,
|
27
|
+
report_interval: report_interval,
|
28
|
+
job_invocation_id: job_invocation.id,
|
29
|
+
correlation_id: correlation_id
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def run(event = nil)
|
34
|
+
# Handle skip events
|
35
|
+
return if event == Dynflow::Action::Skip
|
36
|
+
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
def rescue_strategy_for_self
|
41
|
+
Dynflow::Action::Rescue::Skip
|
42
|
+
end
|
43
|
+
|
44
|
+
def done?(current_status = invocation_status)
|
45
|
+
job_invocation.finished? || current_status.map { |_host_id, task_status| task_status['report_done'] }.all?
|
46
|
+
end
|
47
|
+
|
48
|
+
# noop, we don't want to do anything when the polling task starts
|
49
|
+
def invoke_external_task
|
50
|
+
poll_external_task
|
51
|
+
end
|
52
|
+
|
53
|
+
def poll_external_task
|
54
|
+
current_status = invocation_status
|
55
|
+
report_job_progress(current_status)
|
56
|
+
# record the current state and increment the sequence for the next invocation
|
57
|
+
{
|
58
|
+
invocation_status: current_status,
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def poll_intervals
|
63
|
+
[report_interval]
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def connector_playbook_job?(job_invocation)
|
69
|
+
puts "Job invocation id: #{job_invocation&.remote_execution_feature_id}, feature id: #{connector_feature_id}"
|
70
|
+
job_invocation&.remote_execution_feature_id == connector_feature_id
|
71
|
+
end
|
72
|
+
|
73
|
+
def connector_feature_id
|
74
|
+
self.class.connector_feature_id
|
75
|
+
end
|
76
|
+
|
77
|
+
def invocation_inputs(job_invocation)
|
78
|
+
Hash[
|
79
|
+
job_invocation.pattern_template_invocations.first.input_values.map do |input_value|
|
80
|
+
[input_value.template_input.name, input_value.value]
|
81
|
+
end
|
82
|
+
]
|
83
|
+
end
|
84
|
+
|
85
|
+
def job_invocation
|
86
|
+
@job_invocation ||= JobInvocation.find(input['job_invocation_id'])
|
87
|
+
end
|
88
|
+
|
89
|
+
def report_interval
|
90
|
+
@report_interval ||= input['report_interval']
|
91
|
+
end
|
92
|
+
|
93
|
+
def correlation_id
|
94
|
+
@correlation_id ||= input['correlation_id']
|
95
|
+
end
|
96
|
+
|
97
|
+
def host_status(host)
|
98
|
+
external_task&.dig('invocation_status', host)
|
99
|
+
end
|
100
|
+
|
101
|
+
def sequence(host)
|
102
|
+
host_status(host)&.fetch('sequence', 0).to_i
|
103
|
+
end
|
104
|
+
|
105
|
+
def host_done?(host)
|
106
|
+
ActiveModel::Type::Boolean.new.cast(host_status(host)&.fetch('report_done', nil))
|
107
|
+
end
|
108
|
+
|
109
|
+
def report_url
|
110
|
+
input['report_url']
|
111
|
+
end
|
112
|
+
|
113
|
+
def invocation_status
|
114
|
+
Hash[job_invocation.targeting.hosts.map do |host|
|
115
|
+
next unless host.insights&.uuid
|
116
|
+
[
|
117
|
+
host.insights.uuid,
|
118
|
+
task_status(job_invocation.sub_task_for_host(host), host.insights.uuid),
|
119
|
+
]
|
120
|
+
end.compact]
|
121
|
+
end
|
122
|
+
|
123
|
+
def task_status(host_task, host_name)
|
124
|
+
unless host_task
|
125
|
+
return { 'state' => 'unknown' }
|
126
|
+
end
|
127
|
+
|
128
|
+
{
|
129
|
+
'state' => host_task.state,
|
130
|
+
'output' => host_task.main_action.live_output.map { |line| line['output'] }.join("\n"),
|
131
|
+
'exit_status' => host_task.main_action.exit_status,
|
132
|
+
'sequence' => sequence(host_name),
|
133
|
+
'report_done' => host_done?(host_name),
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
def report_job_progress(invocation_status)
|
138
|
+
generator = InsightsCloud::Generators::PlaybookProgressGenerator.new(correlation_id)
|
139
|
+
|
140
|
+
invocation_status.each do |host_name, status|
|
141
|
+
# skip host if the host already reported that it's finished
|
142
|
+
next if status['report_done']
|
143
|
+
|
144
|
+
unless status['state'] == 'unknown'
|
145
|
+
sequence = status['sequence']
|
146
|
+
generator.host_progress_message(host_name, status['output'], sequence)
|
147
|
+
status['sequence'] = sequence + 1 # increase the sequence for each host
|
148
|
+
end
|
149
|
+
|
150
|
+
if status['state'] == 'stopped'
|
151
|
+
generator.host_finished_message(host_name, status['exit_status'])
|
152
|
+
status['report_done'] = true
|
153
|
+
end
|
154
|
+
end
|
155
|
+
generator.job_finished_message if done?(invocation_status)
|
156
|
+
|
157
|
+
send_report(generator.generate)
|
158
|
+
end
|
159
|
+
|
160
|
+
def send_report(report)
|
161
|
+
execute_cloud_request(
|
162
|
+
method: :post,
|
163
|
+
url: report_url,
|
164
|
+
content_type: 'application/vnd.redhat.playbook-sat.v3+jsonl',
|
165
|
+
payload: {
|
166
|
+
file: wrap_report(report),
|
167
|
+
multipart: true,
|
168
|
+
}
|
169
|
+
)
|
170
|
+
end
|
171
|
+
|
172
|
+
# RestClient has to accept an object that responds to :read, :path and :content_type methods
|
173
|
+
# to properly generate a multipart message.
|
174
|
+
# see: https://github.com/rest-client/rest-client/blob/2c72a2e77e2e87d25ff38feba0cf048d51bd5eca/lib/restclient/payload.rb#L161
|
175
|
+
def wrap_report(report)
|
176
|
+
obj = StringIO.new(report)
|
177
|
+
def obj.content_type
|
178
|
+
'application/vnd.redhat.playbook-sat.v3+jsonl'
|
179
|
+
end
|
180
|
+
|
181
|
+
def obj.path
|
182
|
+
'file'
|
183
|
+
end
|
184
|
+
|
185
|
+
obj
|
186
|
+
end
|
187
|
+
|
188
|
+
def logger
|
189
|
+
action_logger
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module InsightsCloud
|
2
|
+
module Generators
|
3
|
+
class PlaybookProgressGenerator
|
4
|
+
attr_reader :correlation_id
|
5
|
+
def initialize(correlation_id)
|
6
|
+
@messages = []
|
7
|
+
@correlation_id = correlation_id
|
8
|
+
end
|
9
|
+
|
10
|
+
def host_progress_message(host_name, output, sequence)
|
11
|
+
@messages << {
|
12
|
+
"type": "playbook_run_update",
|
13
|
+
"version": 3,
|
14
|
+
"correlation_id": correlation_id,
|
15
|
+
"sequence": sequence,
|
16
|
+
"host": host_name,
|
17
|
+
"console": output,
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def host_finished_message(host_name, exit_code)
|
22
|
+
@messages << {
|
23
|
+
"type": "playbook_run_finished",
|
24
|
+
"version": 3,
|
25
|
+
"correlation_id": correlation_id,
|
26
|
+
"host": host_name,
|
27
|
+
"status": exit_code == 0 ? 'success' : 'failure',
|
28
|
+
"connection_code": 0,
|
29
|
+
"execution_code": exit_code,
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def job_finished_message
|
34
|
+
@messages << {
|
35
|
+
"type": "playbook_run_completed",
|
36
|
+
"version": 3,
|
37
|
+
"correlation_id": correlation_id,
|
38
|
+
"status": "success",
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def generate
|
43
|
+
@messages.map do |message|
|
44
|
+
message.to_json
|
45
|
+
end.join("\n")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/tasks/insights.rake
CHANGED
@@ -19,4 +19,17 @@ namespace :rh_cloud_insights do
|
|
19
19
|
|
20
20
|
puts "Deleted #{deleted_count} insights statuses"
|
21
21
|
end
|
22
|
+
|
23
|
+
desc "Re-announce all organizations into Sources on RH cloud."
|
24
|
+
task announce_to_sources: [:environment] do
|
25
|
+
logger = Logging::Logger.new(STDOUT)
|
26
|
+
Organization.unscoped.each do |org|
|
27
|
+
presence = ForemanRhCloud::CloudPresence.new(org, logger)
|
28
|
+
presence.announce_to_sources
|
29
|
+
rescue StandardError => ex
|
30
|
+
logger.warn(ex)
|
31
|
+
end
|
32
|
+
|
33
|
+
logger.info('Reannounced all organizations')
|
34
|
+
end
|
22
35
|
end
|
data/package.json
CHANGED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'test_plugin_helper'
|
3
|
+
|
4
|
+
module InsightsCloud::Api
|
5
|
+
class CloudRequestControllerTest < ActionController::TestCase
|
6
|
+
tests Api::V2::RhCloud::CloudRequestController
|
7
|
+
|
8
|
+
setup do
|
9
|
+
@test_org = FactoryBot.create(:organization)
|
10
|
+
end
|
11
|
+
|
12
|
+
test 'Fails for unknown directives' do
|
13
|
+
request_params = run_playbook_request
|
14
|
+
request_params['directive'] = 'not-valid'
|
15
|
+
|
16
|
+
post :update, params: request_params
|
17
|
+
|
18
|
+
assert_response :bad_request
|
19
|
+
end
|
20
|
+
|
21
|
+
test 'Starts playbook run for correct directive' do
|
22
|
+
Setting[:rh_cloud_token] = 'MOCK_TOKEN'
|
23
|
+
host1 = FactoryBot.create(:host, :with_insights_hits)
|
24
|
+
host1.insights.uuid = 'TEST_UUID1'
|
25
|
+
host1.insights.save!
|
26
|
+
host2 = FactoryBot.create(:host, :with_insights_hits)
|
27
|
+
host2.insights.uuid = 'TEST_UUID2'
|
28
|
+
host2.insights.save!
|
29
|
+
|
30
|
+
mock_composer = mock('composer')
|
31
|
+
::JobInvocationComposer.expects(:for_feature).with do |feature, host_ids, params|
|
32
|
+
feature == :rh_cloud_connector_run_playbook &&
|
33
|
+
host_ids.first == host1.id &&
|
34
|
+
host_ids.last == host2.id
|
35
|
+
end.returns(mock_composer)
|
36
|
+
mock_composer.expects(:trigger!)
|
37
|
+
mock_composer.expects(:job_invocation)
|
38
|
+
|
39
|
+
post :update, params: run_playbook_request
|
40
|
+
|
41
|
+
assert_response :success
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def run_playbook_request
|
47
|
+
request_json = <<-REQUEST
|
48
|
+
{
|
49
|
+
"type": "data",
|
50
|
+
"message_id": "a6a7d866-7de0-409a-84e0-3c56c4171bb7",
|
51
|
+
"version": 1,
|
52
|
+
"sent": "2021-01-12T15:30:08+00:00",
|
53
|
+
"directive": "playbook-sat",
|
54
|
+
"metadata": {
|
55
|
+
"operation": "run",
|
56
|
+
"return_url": "https://cloud.redhat.com/api/v1/ingres/upload",
|
57
|
+
"correlation_id": "6684b9dd-0d16-42c1-b13a-9f45be59e3b6",
|
58
|
+
"playbook_run_name": "Human-readable playbook run name",
|
59
|
+
"playbook_run_url": "https://console.redhat.com/insights/remediations/1234",
|
60
|
+
"sat_id": "aa3b1faa-56f3-4d14-8258-615d11e20060",
|
61
|
+
"sat_org_id": "#{FactoryBot.create(:organization).id}",
|
62
|
+
"initiator_user_id": "4efca34c6d9ae05ef7c3d7a7424e6370d198159a841ae005084888a9a4529e27",
|
63
|
+
"hosts": "TEST_UUID1,TEST_UUID2",
|
64
|
+
"response_interval": "30",
|
65
|
+
"response_full": "false"
|
66
|
+
},
|
67
|
+
"content": ""
|
68
|
+
}
|
69
|
+
REQUEST
|
70
|
+
|
71
|
+
request = JSON.parse(request_json)
|
72
|
+
|
73
|
+
request['content'] = "\"#{Base64.encode64('https://cloud.redhat.com/api/v1/remediations/1234/playbook')}\""
|
74
|
+
|
75
|
+
request
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'test_plugin_helper'
|
3
|
+
require 'foreman_tasks/test_helpers'
|
4
|
+
require "#{ForemanTasks::Engine.root}/test/support/dummy_dynflow_action"
|
5
|
+
|
6
|
+
class ConnectorPlaybookExecutionReporterTaskTest < ActiveSupport::TestCase
|
7
|
+
include ForemanTasks::TestHelpers::WithInThreadExecutor
|
8
|
+
|
9
|
+
# override default send behavior for the test
|
10
|
+
class TestConnectorPlaybookExecutionReporterTask < InsightsCloud::Async::ConnectorPlaybookExecutionReporterTask
|
11
|
+
def send_report(report)
|
12
|
+
output[:saved_reports] = (output[:saved_reports] || []) << report
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
setup do
|
17
|
+
feature = RemoteExecutionFeature.register(
|
18
|
+
:rh_cloud_connector_run_playbook,
|
19
|
+
N_('Run RH Cloud playbook'),
|
20
|
+
description: N_('Run playbook genrated by Red Hat remediations app'),
|
21
|
+
host_action_button: false,
|
22
|
+
provided_inputs: ['playbook_url', 'report_url', 'correlation_id', 'report_interval']
|
23
|
+
)
|
24
|
+
|
25
|
+
puts "REX Register: #{feature.id}"
|
26
|
+
|
27
|
+
@job_invocation = generate_job_invocation
|
28
|
+
|
29
|
+
# reset connector feature ID cache
|
30
|
+
TestConnectorPlaybookExecutionReporterTask.instance_variable_set(:@connector_feature_id, nil)
|
31
|
+
|
32
|
+
puts RemoteExecutionFeature.all.to_a
|
33
|
+
end
|
34
|
+
|
35
|
+
test 'It reports finish playbook messages' do
|
36
|
+
TestConnectorPlaybookExecutionReporterTask.any_instance.stubs(:done?).returns(true)
|
37
|
+
|
38
|
+
actual = ForemanTasks.sync_task(TestConnectorPlaybookExecutionReporterTask, @job_invocation)
|
39
|
+
|
40
|
+
actual_report = actual.output[:saved_reports].first.to_s
|
41
|
+
|
42
|
+
assert_equal 1, actual.output[:saved_reports].size
|
43
|
+
assert_not_nil actual_report
|
44
|
+
actual_jsonl = read_jsonl(actual_report)
|
45
|
+
|
46
|
+
assert_not_nil actual_report_finished = actual_jsonl.find { |l| l['type'] == 'playbook_run_completed' }
|
47
|
+
assert_equal 'TEST_CORRELATION', actual_report_finished['correlation_id']
|
48
|
+
assert_equal 'success', actual_report_finished['status']
|
49
|
+
|
50
|
+
assert_not_nil actual_host_finished = actual_jsonl.find { |l| l['type'] == 'playbook_run_finished' && l['host'] == @host1.insights.uuid }
|
51
|
+
assert_equal 'TEST_CORRELATION', actual_host_finished['correlation_id']
|
52
|
+
assert_equal 'success', actual_host_finished['status']
|
53
|
+
end
|
54
|
+
|
55
|
+
test 'It reports single progress message for done host' do
|
56
|
+
TestConnectorPlaybookExecutionReporterTask.any_instance.stubs(:done?).returns(false, true)
|
57
|
+
|
58
|
+
actual = ForemanTasks.sync_task(TestConnectorPlaybookExecutionReporterTask, @job_invocation)
|
59
|
+
|
60
|
+
actual_report = actual.output[:saved_reports].first.to_s
|
61
|
+
|
62
|
+
assert_equal 1, actual.output[:saved_reports].size
|
63
|
+
assert_not_nil actual_report
|
64
|
+
actual_jsonl = read_jsonl(actual_report)
|
65
|
+
|
66
|
+
actual_host_updates = actual_jsonl
|
67
|
+
.select { |l| l['type'] == 'playbook_run_update' && l['host'] == @host1.insights.uuid }
|
68
|
+
assert_equal 1, actual_host_updates.size
|
69
|
+
assert_equal 0, actual_host_updates.first['sequence']
|
70
|
+
end
|
71
|
+
|
72
|
+
test 'It reports two progress messages for in progress host' do
|
73
|
+
TestConnectorPlaybookExecutionReporterTask.any_instance.stubs(:done?).returns(false, false, true)
|
74
|
+
|
75
|
+
host1_task = @job_invocation.template_invocations.joins(:host).where(hosts: {name: @host1.name}).first.run_host_job_task
|
76
|
+
host1_task.state = 'running'
|
77
|
+
host1_task.save!
|
78
|
+
|
79
|
+
actual = ForemanTasks.sync_task(TestConnectorPlaybookExecutionReporterTask, @job_invocation)
|
80
|
+
|
81
|
+
assert_equal 2, actual.output[:saved_reports].size
|
82
|
+
|
83
|
+
first_report = actual.output[:saved_reports].first.to_s
|
84
|
+
actual_jsonl = read_jsonl(first_report)
|
85
|
+
|
86
|
+
actual_host_updates = actual_jsonl
|
87
|
+
.select { |l| l['type'] == 'playbook_run_update' && l['host'] == @host1.insights.uuid }
|
88
|
+
assert_equal 1, actual_host_updates.size
|
89
|
+
assert_equal 0, actual_host_updates.first['sequence']
|
90
|
+
|
91
|
+
actual_host_updates = actual_jsonl
|
92
|
+
.select { |l| l['type'] == 'playbook_run_update' && l['host'] == @host2.insights.uuid }
|
93
|
+
assert_equal 1, actual_host_updates.size
|
94
|
+
assert_equal 0, actual_host_updates.first['sequence']
|
95
|
+
|
96
|
+
second_report = actual.output[:saved_reports].last.to_s
|
97
|
+
actual_jsonl = read_jsonl(second_report)
|
98
|
+
|
99
|
+
actual_host_updates = actual_jsonl
|
100
|
+
.select { |l| l['type'] == 'playbook_run_update' && l['host'] == @host1.insights.uuid }
|
101
|
+
assert_equal 1, actual_host_updates.size
|
102
|
+
assert_equal 1, actual_host_updates.first['sequence']
|
103
|
+
|
104
|
+
actual_host_updates = actual_jsonl
|
105
|
+
.select { |l| l['type'] == 'playbook_run_update' && l['host'] == @host2.insights.uuid }
|
106
|
+
assert_equal 0, actual_host_updates.size
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def generate_job_invocation
|
112
|
+
job_template = FactoryBot.build(
|
113
|
+
:job_template,
|
114
|
+
:template => 'BLEH'
|
115
|
+
)
|
116
|
+
feature = RemoteExecutionFeature.feature!(:rh_cloud_connector_run_playbook).id
|
117
|
+
|
118
|
+
puts "Generated feature: #{feature}"
|
119
|
+
|
120
|
+
job_invocation = FactoryBot.create(
|
121
|
+
:job_invocation,
|
122
|
+
remote_execution_feature_id: feature,
|
123
|
+
task_id: FactoryBot.create(:dynflow_task).id
|
124
|
+
)
|
125
|
+
|
126
|
+
job_template.template_inputs << playbook_url_input = FactoryBot.build(:template_input,
|
127
|
+
:name => 'playbook_url',
|
128
|
+
:input_type => 'user',
|
129
|
+
:required => true)
|
130
|
+
job_template.template_inputs << report_url_input = FactoryBot.build(:template_input,
|
131
|
+
:name => 'report_url',
|
132
|
+
:input_type => 'user',
|
133
|
+
:required => true)
|
134
|
+
job_template.template_inputs << correlation_id_input = FactoryBot.build(:template_input,
|
135
|
+
:name => 'correlation_id',
|
136
|
+
:input_type => 'user',
|
137
|
+
:required => true)
|
138
|
+
job_template.template_inputs << report_interval_input = FactoryBot.build(:template_input,
|
139
|
+
:name => 'report_interval',
|
140
|
+
:input_type => 'user',
|
141
|
+
:required => true)
|
142
|
+
|
143
|
+
template_invocation = FactoryBot.build(:template_invocation,
|
144
|
+
:template => job_template,
|
145
|
+
:job_invocation => job_invocation
|
146
|
+
)
|
147
|
+
|
148
|
+
template_invocation.input_values << FactoryBot.create(
|
149
|
+
:template_invocation_input_value,
|
150
|
+
:template_invocation => template_invocation,
|
151
|
+
:template_input => playbook_url_input,
|
152
|
+
:value => 'http://example.com/TEST_PLAYBOOK'
|
153
|
+
)
|
154
|
+
template_invocation.input_values << FactoryBot.create(
|
155
|
+
:template_invocation_input_value,
|
156
|
+
:template_invocation => template_invocation,
|
157
|
+
:template_input => report_url_input,
|
158
|
+
:value => 'http://example.com/TEST_REPORT'
|
159
|
+
)
|
160
|
+
template_invocation.input_values << FactoryBot.create(
|
161
|
+
:template_invocation_input_value,
|
162
|
+
:template_invocation => template_invocation,
|
163
|
+
:template_input => correlation_id_input,
|
164
|
+
:value => 'TEST_CORRELATION'
|
165
|
+
)
|
166
|
+
template_invocation.input_values << FactoryBot.create(
|
167
|
+
:template_invocation_input_value,
|
168
|
+
:template_invocation => template_invocation,
|
169
|
+
:template_input => report_interval_input,
|
170
|
+
:value => '1'
|
171
|
+
)
|
172
|
+
|
173
|
+
@host1 = FactoryBot.create(:host, :with_insights_hits, name: 'host1')
|
174
|
+
@host1.insights.uuid = 'TEST_UUID1'
|
175
|
+
@host1.insights.save!
|
176
|
+
@host2 = FactoryBot.create(:host, :with_insights_hits, name: 'host2')
|
177
|
+
@host2.insights.uuid = 'TEST_UUID2'
|
178
|
+
@host2.insights.save!
|
179
|
+
|
180
|
+
targeting = FactoryBot.create(:targeting, hosts: [@host1, @host2])
|
181
|
+
job_invocation.targeting = targeting
|
182
|
+
job_invocation.save!
|
183
|
+
|
184
|
+
job_invocation.template_invocations << FactoryBot.create(
|
185
|
+
:template_invocation,
|
186
|
+
run_host_job_task: FactoryBot.create(:dynflow_task),
|
187
|
+
host_id: @host1.id
|
188
|
+
)
|
189
|
+
job_invocation.template_invocations << FactoryBot.create(
|
190
|
+
:template_invocation,
|
191
|
+
run_host_job_task: FactoryBot.create(:dynflow_task),
|
192
|
+
host_id: @host2.id
|
193
|
+
)
|
194
|
+
|
195
|
+
fake_output = (1..5).map do |i|
|
196
|
+
{ 'timestamp' => (Time.now - (5 - i)).to_f, 'output' => "#{i}\n" }
|
197
|
+
end
|
198
|
+
Support::DummyDynflowAction.any_instance.stubs(:live_output).returns(fake_output)
|
199
|
+
Support::DummyDynflowAction.any_instance.stubs(:exit_status).returns(0)
|
200
|
+
|
201
|
+
job_invocation
|
202
|
+
end
|
203
|
+
|
204
|
+
def read_jsonl(jsonl)
|
205
|
+
jsonl.lines.map { |l| JSON.parse(l) }
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'test_plugin_helper'
|
2
|
+
|
3
|
+
class PlaybookProgressGeneratorTest < ActiveSupport::TestCase
|
4
|
+
setup do
|
5
|
+
@correlation_id = 'CORRELATION_ID'
|
6
|
+
@generator = InsightsCloud::Generators::PlaybookProgressGenerator.new(@correlation_id)
|
7
|
+
end
|
8
|
+
|
9
|
+
test 'Outputs host progress message' do
|
10
|
+
@generator.host_progress_message('test_host', 'test_output', 0)
|
11
|
+
|
12
|
+
actual = @generator.generate
|
13
|
+
actual_message = JSON.parse(actual)
|
14
|
+
|
15
|
+
assert_equal "playbook_run_update", actual_message["type"]
|
16
|
+
assert_equal 3, actual_message["version"]
|
17
|
+
assert_equal @correlation_id, actual_message["correlation_id"]
|
18
|
+
assert_equal 0, actual_message["sequence"]
|
19
|
+
assert_equal 'test_host', actual_message["host"]
|
20
|
+
assert_equal 'test_output', actual_message["console"]
|
21
|
+
end
|
22
|
+
|
23
|
+
test 'Outputs host finished with error message' do
|
24
|
+
@generator.host_finished_message('test_host', 100)
|
25
|
+
|
26
|
+
actual = @generator.generate
|
27
|
+
actual_message = JSON.parse(actual)
|
28
|
+
|
29
|
+
assert_equal "playbook_run_finished", actual_message["type"]
|
30
|
+
assert_equal 3, actual_message["version"]
|
31
|
+
assert_equal @correlation_id, actual_message["correlation_id"]
|
32
|
+
assert_equal 'test_host', actual_message["host"]
|
33
|
+
assert_equal 'failure', actual_message["status"]
|
34
|
+
assert_equal 0, actual_message["connection_code"]
|
35
|
+
assert_equal 100, actual_message["execution_code"]
|
36
|
+
end
|
37
|
+
|
38
|
+
test 'Outputs host finished successfully message' do
|
39
|
+
@generator.host_finished_message('test_host', 0)
|
40
|
+
|
41
|
+
actual = @generator.generate
|
42
|
+
actual_message = JSON.parse(actual)
|
43
|
+
|
44
|
+
assert_equal "playbook_run_finished", actual_message["type"]
|
45
|
+
assert_equal 3, actual_message["version"]
|
46
|
+
assert_equal @correlation_id, actual_message["correlation_id"]
|
47
|
+
assert_equal 'test_host', actual_message["host"]
|
48
|
+
assert_equal 'success', actual_message["status"]
|
49
|
+
assert_equal 0, actual_message["connection_code"]
|
50
|
+
assert_equal 0, actual_message["execution_code"]
|
51
|
+
end
|
52
|
+
|
53
|
+
test 'Outputs job finished message' do
|
54
|
+
@generator.job_finished_message
|
55
|
+
|
56
|
+
actual = @generator.generate
|
57
|
+
actual_message = JSON.parse(actual)
|
58
|
+
|
59
|
+
assert_equal "playbook_run_completed", actual_message["type"]
|
60
|
+
assert_equal 3, actual_message["version"]
|
61
|
+
assert_equal @correlation_id, actual_message["correlation_id"]
|
62
|
+
assert_equal "success", actual_message["status"]
|
63
|
+
end
|
64
|
+
|
65
|
+
test 'Outputs a valid JSONL format' do
|
66
|
+
@generator.host_finished_message('test_host1', 0)
|
67
|
+
@generator.host_finished_message('test_host2', 0)
|
68
|
+
|
69
|
+
actual = @generator.generate.lines
|
70
|
+
actual_message1 = JSON.parse(actual[0])
|
71
|
+
assert_equal 'test_host1', actual_message1['host']
|
72
|
+
actual_message2 = JSON.parse(actual[1])
|
73
|
+
assert_equal 'test_host2', actual_message2['host']
|
74
|
+
end
|
75
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'test_plugin_helper'
|
2
2
|
|
3
|
-
class
|
3
|
+
class HitRemediationsRetrieverTest < ActiveSupport::TestCase
|
4
4
|
setup do
|
5
5
|
@host1 = FactoryBot.create(:host)
|
6
6
|
@host2 = FactoryBot.create(:host)
|
@@ -16,7 +16,7 @@ class RemediationsRetrieverTest < ActiveSupport::TestCase
|
|
16
16
|
host2_hit1 = FactoryBot.create(:insights_hit, rule_id: rule1.rule_id, host_id: @host2.id)
|
17
17
|
|
18
18
|
pairs = [{'hit_id' => host1_hit1.id, 'resolution_id' => rule1_remediation1.id}, {'hit_id' => host2_hit1.id, 'resolution_id' => rule1_remediation1.id}]
|
19
|
-
retriever = ForemanRhCloud::
|
19
|
+
retriever = ForemanRhCloud::HitRemediationsRetriever.new(pairs)
|
20
20
|
|
21
21
|
actual_request = retriever.send(:playbook_request)
|
22
22
|
|
@@ -39,7 +39,7 @@ class RemediationsRetrieverTest < ActiveSupport::TestCase
|
|
39
39
|
host2_hit1 = FactoryBot.create(:insights_hit, rule_id: rule1.rule_id, host_id: @host2.id)
|
40
40
|
|
41
41
|
pairs = [{'hit_id' => host1_hit1.id, 'resolution_id' => rule1_remediation1.id}, {'hit_id' => host2_hit1.id, 'resolution_id' => rule1_remediation2.id}]
|
42
|
-
retriever = ForemanRhCloud::
|
42
|
+
retriever = ForemanRhCloud::HitRemediationsRetriever.new(pairs)
|
43
43
|
|
44
44
|
actual_request = retriever.send(:playbook_request)
|
45
45
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'test_plugin_helper'
|
2
|
+
|
3
|
+
class UrlRemediationsRetrieverTest < ActiveSupport::TestCase
|
4
|
+
test 'Calls the given url' do
|
5
|
+
Setting[:rh_cloud_token] = 'TEST_TOKEN'
|
6
|
+
retreiver = ForemanRhCloud::UrlRemediationsRetriever.new(
|
7
|
+
url: 'http://test.example.com',
|
8
|
+
payload: 'TEST_PAYLOAD',
|
9
|
+
headers: {
|
10
|
+
custom1: 'TEST_HEADER',
|
11
|
+
}
|
12
|
+
)
|
13
|
+
|
14
|
+
response = mock('response')
|
15
|
+
response.stubs(:body).returns('TEST_RESPONSE')
|
16
|
+
retreiver.expects(:execute_cloud_request).with do |params|
|
17
|
+
params[:method] == :get &&
|
18
|
+
params[:url] == 'http://test.example.com' &&
|
19
|
+
params[:headers][:custom1] == 'TEST_HEADER' &&
|
20
|
+
params[:payload] == "\"TEST_PAYLOAD\""
|
21
|
+
end.returns(response)
|
22
|
+
|
23
|
+
actual = retreiver.create_playbook
|
24
|
+
|
25
|
+
assert_equal 'TEST_RESPONSE', actual
|
26
|
+
end
|
27
|
+
end
|