foreman_remote_execution 14.0.2 → 14.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +34 -17
  3. data/app/helpers/remote_execution_helper.rb +2 -2
  4. data/app/lib/actions/remote_execution/proxy_action.rb +10 -5
  5. data/app/lib/actions/remote_execution/run_host_job.rb +1 -1
  6. data/app/lib/actions/remote_execution/template_invocation_progress_logging.rb +2 -3
  7. data/app/views/api/v2/job_invocations/hosts.json.rabl +15 -0
  8. data/config/routes.rb +1 -0
  9. data/db/migrate/20240312133027_extend_template_invocation_events.rb +19 -0
  10. data/lib/foreman_remote_execution/version.rb +1 -1
  11. data/webpack/JobInvocationDetail/JobInvocationActions.js +1 -1
  12. data/webpack/JobInvocationDetail/JobInvocationConstants.js +84 -0
  13. data/webpack/JobInvocationDetail/JobInvocationDetail.scss +0 -1
  14. data/webpack/JobInvocationDetail/JobInvocationHostTable.js +210 -0
  15. data/webpack/JobInvocationDetail/JobInvocationSelectors.js +2 -2
  16. data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +5 -1
  17. data/webpack/JobInvocationDetail/__tests__/fixtures.js +9 -0
  18. data/webpack/JobInvocationDetail/index.js +56 -34
  19. data/webpack/__mocks__/foremanReact/components/HostDetails/DetailsCard/DefaultLoaderEmptyState.js +1 -2
  20. data/webpack/react_app/components/RecentJobsCard/JobStatusIcon.js +38 -7
  21. data/webpack/react_app/components/RecentJobsCard/constants.js +4 -0
  22. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/HostStatus.test.js.snap +1 -1
  23. data/webpack/react_app/components/TargetingHosts/components/HostStatus.js +6 -6
  24. metadata +6 -94
  25. data/.babelrc.js +0 -3
  26. data/.eslintignore +0 -3
  27. data/.eslintrc +0 -13
  28. data/.github/workflows/js_ci.yml +0 -32
  29. data/.github/workflows/release.yml +0 -16
  30. data/.github/workflows/ruby_ci.yml +0 -19
  31. data/.gitignore +0 -19
  32. data/.packit.yaml +0 -45
  33. data/.prettierrc +0 -4
  34. data/.rubocop.yml +0 -105
  35. data/.rubocop_todo.yml +0 -516
  36. data/.tx/config +0 -10
  37. data/Gemfile +0 -5
  38. data/app/mailers/.gitkeep +0 -0
  39. data/app/views/dashboard/.gitkeep +0 -0
  40. data/foreman_remote_execution.gemspec +0 -33
  41. data/jsconfig.json +0 -8
  42. data/test/benchmark/run_hosts_job_benchmark.rb +0 -70
  43. data/test/benchmark/targeting_benchmark.rb +0 -31
  44. data/test/factories/foreman_remote_execution_factories.rb +0 -147
  45. data/test/functional/api/v2/foreign_input_sets_controller_test.rb +0 -58
  46. data/test/functional/api/v2/job_invocations_controller_test.rb +0 -446
  47. data/test/functional/api/v2/job_templates_controller_test.rb +0 -110
  48. data/test/functional/api/v2/registration_controller_test.rb +0 -73
  49. data/test/functional/api/v2/remote_execution_features_controller_test.rb +0 -34
  50. data/test/functional/api/v2/template_invocations_controller_test.rb +0 -33
  51. data/test/functional/cockpit_controller_test.rb +0 -16
  52. data/test/functional/job_invocations_controller_test.rb +0 -132
  53. data/test/functional/job_templates_controller_test.rb +0 -31
  54. data/test/functional/ui_job_wizard_controller_test.rb +0 -16
  55. data/test/graphql/mutations/job_invocations/create_test.rb +0 -58
  56. data/test/graphql/queries/job_invocation_query_test.rb +0 -31
  57. data/test/graphql/queries/job_invocations_query_test.rb +0 -35
  58. data/test/helpers/remote_execution_helper_test.rb +0 -46
  59. data/test/support/remote_execution_helper.rb +0 -5
  60. data/test/test_plugin_helper.rb +0 -9
  61. data/test/unit/actions/run_host_job_test.rb +0 -115
  62. data/test/unit/actions/run_hosts_job_test.rb +0 -214
  63. data/test/unit/api_params_test.rb +0 -25
  64. data/test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb +0 -29
  65. data/test/unit/concerns/host_extensions_test.rb +0 -219
  66. data/test/unit/concerns/nic_extensions_test.rb +0 -9
  67. data/test/unit/execution_task_status_mapper_test.rb +0 -92
  68. data/test/unit/input_template_renderer_test.rb +0 -503
  69. data/test/unit/job_invocation_composer_test.rb +0 -974
  70. data/test/unit/job_invocation_report_template_test.rb +0 -60
  71. data/test/unit/job_invocation_test.rb +0 -232
  72. data/test/unit/job_template_effective_user_test.rb +0 -37
  73. data/test/unit/job_template_test.rb +0 -316
  74. data/test/unit/remote_execution_feature_test.rb +0 -86
  75. data/test/unit/remote_execution_provider_test.rb +0 -298
  76. data/test/unit/renderer_scope_input_test.rb +0 -49
  77. data/test/unit/targeting_test.rb +0 -206
  78. data/test/unit/template_invocation_input_value_test.rb +0 -38
@@ -1,974 +0,0 @@
1
- require 'test_plugin_helper'
2
- RemoteExecutionProvider.register(:Ansible, OpenStruct)
3
- RemoteExecutionProvider.register(:Mcollective, OpenStruct)
4
-
5
- class JobInvocationComposerTest < ActiveSupport::TestCase
6
- before do
7
- setup_user('create', 'template_invocations')
8
- setup_user('view', 'job_templates', 'name ~ trying*')
9
- setup_user('create', 'job_templates', 'name ~ trying*')
10
- setup_user('view', 'job_invocations')
11
- setup_user('create', 'job_invocations')
12
- setup_user('view', 'bookmarks')
13
- setup_user('create', 'bookmarks')
14
- setup_user('edit', 'bookmarks')
15
- setup_user('view', 'hosts')
16
- setup_user('create', 'hosts')
17
- end
18
-
19
- class AnsibleInputs < RemoteExecutionProvider
20
- class << self
21
- def provider_input_namespace
22
- :ansible
23
- end
24
-
25
- def provider_inputs
26
- [
27
- ForemanRemoteExecution::ProviderInput.new(name: 'tags', label: 'Tags', value: 'fooo', value_type: 'plain'),
28
- ForemanRemoteExecution::ProviderInput.new(name: 'tags_flag', label: 'Tags Flag', value: '--tags', options: "--tags\n--skip-tags"),
29
- ]
30
- end
31
- end
32
- end
33
- RemoteExecutionProvider.register(:AnsibleInputs, AnsibleInputs)
34
-
35
- let(:trying_job_template_1) { FactoryBot.create(:job_template, :job_category => 'trying_job_template_1', :name => 'trying1', :provider_type => 'SSH') }
36
- let(:trying_job_template_2) { FactoryBot.create(:job_template, :job_category => 'trying_job_template_2', :name => 'trying2', :provider_type => 'Mcollective') }
37
- let(:trying_job_template_3) { FactoryBot.create(:job_template, :job_category => 'trying_job_template_1', :name => 'trying3', :provider_type => 'SSH') }
38
- let(:trying_job_template_5) { FactoryBot.create(:job_template, :job_category => 'trying_job_template_5', :name => 'trying5', :provider_type => 'SSH') }
39
- let(:unauthorized_job_template_1) { FactoryBot.create(:job_template, :job_category => 'trying_job_template_1', :name => 'unauth1', :provider_type => 'SSH') }
40
- let(:unauthorized_job_template_2) { FactoryBot.create(:job_template, :job_category => 'unauthorized_job_template_2', :name => 'unauth2', :provider_type => 'Ansible') }
41
-
42
- let(:provider_inputs_job_template) { FactoryBot.create(:job_template, :job_category => 'trying_test_inputs', :name => 'trying provider inputs', :provider_type => 'AnsibleInputs') }
43
-
44
- let(:input1) { FactoryBot.create(:template_input, :template => trying_job_template_1, :input_type => 'user') }
45
- let(:input2) { FactoryBot.create(:template_input, :template => trying_job_template_3, :input_type => 'user') }
46
- let(:input3) { FactoryBot.create(:template_input, :template => trying_job_template_1, :input_type => 'user', :required => true) }
47
- let(:input4) { FactoryBot.create(:template_input, :template => provider_inputs_job_template, :input_type => 'user') }
48
- let(:input5) { FactoryBot.create(:template_input, :template => trying_job_template_5, :input_type => 'user', :default => 'value') }
49
- let(:unauthorized_input1) { FactoryBot.create(:template_input, :template => unauthorized_job_template_1, :input_type => 'user') }
50
-
51
- let(:ansible_params) { { } }
52
- let(:ssh_params) { { } }
53
- let(:mcollective_params) { { } }
54
- let(:providers_params) { { :providers => { :ansible => ansible_params, :ssh => ssh_params, :mcollective => mcollective_params } } }
55
-
56
- context 'with general new invocation and empty params' do
57
- let(:params) { {} }
58
- let(:composer) { JobInvocationComposer.from_ui_params(params) }
59
-
60
- describe '#available_templates' do
61
- it 'obeys authorization' do
62
- composer # lazy load composer before stubbing
63
- JobTemplate.expects(:authorized).with(:view_job_templates).returns(JobTemplate.where({}))
64
- composer.available_templates
65
- end
66
- end
67
-
68
- context 'job templates exist' do
69
- before do
70
- trying_job_template_1
71
- trying_job_template_2
72
- trying_job_template_3
73
- unauthorized_job_template_1
74
- unauthorized_job_template_2
75
- end
76
-
77
- describe '#available_templates_for(job_category)' do
78
- it 'find the templates only for a given job name' do
79
- results = composer.available_templates_for(trying_job_template_1.job_category)
80
- assert_includes results, trying_job_template_1
81
- refute_includes results, trying_job_template_2
82
- end
83
-
84
- it 'it respects view permissions' do
85
- results = composer.available_templates_for(trying_job_template_1.job_category)
86
- refute_includes results, unauthorized_job_template_1
87
- end
88
- end
89
-
90
- describe '#available_job_categories' do
91
- let(:job_categories) { composer.available_job_categories }
92
-
93
- it 'find only job names that user is granted to view' do
94
- assert_includes job_categories, trying_job_template_1.job_category
95
- assert_includes job_categories, trying_job_template_2.job_category
96
- refute_includes job_categories, unauthorized_job_template_2.job_category
97
- end
98
-
99
- it 'every job name is listed just once' do
100
- assert_equal job_categories.uniq, job_categories
101
- end
102
- end
103
-
104
- describe '#available_provider_types' do
105
- let(:provider_types) { composer.available_provider_types }
106
-
107
- it 'finds only providers which user is granted to view' do
108
- composer.job_invocation.job_category = 'trying_job_template_1'
109
- assert_includes provider_types, 'SSH'
110
- refute_includes provider_types, 'Mcollective'
111
- refute_includes provider_types, 'Ansible'
112
- end
113
-
114
- it 'every provider type is listed just once' do
115
- assert_equal provider_types.uniq, provider_types
116
- end
117
- end
118
-
119
- describe '#available_template_inputs' do
120
- before do
121
- input1
122
- input2
123
- unauthorized_input1
124
- end
125
-
126
- it 'returns only authorized inputs based on templates' do
127
- assert_includes composer.available_template_inputs, input1
128
- assert_includes composer.available_template_inputs, input2
129
- refute_includes composer.available_template_inputs, unauthorized_input1
130
- end
131
-
132
- context 'params contains job template ids' do
133
- let(:ssh_params) { { :job_template_id => trying_job_template_1.id.to_s } }
134
- let(:ansible_params) { { :job_template_id => '' } }
135
- let(:mcollective_params) { { :job_template_id => '' } }
136
- let(:params) { { :job_invocation => providers_params }.with_indifferent_access }
137
-
138
- it 'finds the inputs only specified job templates' do
139
- assert_includes composer.available_template_inputs, input1
140
- refute_includes composer.available_template_inputs, input2
141
- refute_includes composer.available_template_inputs, unauthorized_input1
142
- end
143
- end
144
- end
145
-
146
- describe '#needs_provider_type_selection?' do
147
- it 'returns true if there are more than one providers respecting authorization' do
148
- composer.stubs(:available_provider_types => [ 'SSH', 'Ansible' ])
149
- assert composer.needs_provider_type_selection?
150
- end
151
-
152
- it 'returns false if there is one provider' do
153
- composer.stubs(:available_provider_types => [ 'SSH' ])
154
- refute composer.needs_provider_type_selection?
155
- end
156
- end
157
-
158
- describe '#displayed_provider_types' do
159
- # nothing to test yet
160
- end
161
-
162
- describe '#templates_for_provider(provider_type)' do
163
- it 'returns all templates for a given provider respecting template permissions' do
164
- trying_job_template_4 = FactoryBot.create(:job_template, :job_category => 'trying_job_template_1', :name => 'trying4', :provider_type => 'Ansible')
165
- result = composer.templates_for_provider('SSH')
166
- assert_includes result, trying_job_template_1
167
- assert_includes result, trying_job_template_3
168
- refute_includes result, unauthorized_job_template_1
169
- refute_includes result, trying_job_template_4
170
-
171
- result = composer.templates_for_provider('Ansible')
172
- refute_includes result, trying_job_template_1
173
- refute_includes result, trying_job_template_3
174
- refute_includes result, unauthorized_job_template_2
175
- assert_includes result, trying_job_template_4
176
- end
177
- end
178
-
179
- describe '#rerun_possible?' do
180
- it 'is true when not rerunning' do
181
- assert_predicate composer, :rerun_possible?
182
- end
183
-
184
- it 'is true when rerunning with pattern tempalte invocations' do
185
- composer.expects(:reruns).returns(1)
186
- composer.job_invocation.expects(:pattern_template_invocations).returns([1])
187
- assert_predicate composer, :rerun_possible?
188
- end
189
-
190
- it 'is false when rerunning without pattern template invocations' do
191
- composer.expects(:reruns).returns(1)
192
- composer.job_invocation.expects(:pattern_template_invocations).returns([])
193
- refute_predicate composer, :rerun_possible?
194
- end
195
- end
196
-
197
- describe '#selected_job_templates' do
198
- it 'returns no template if none was selected through params' do
199
- assert_empty composer.selected_job_templates
200
- end
201
-
202
- context 'extra unavailable templates id were selected' do
203
- let(:unauthorized) { FactoryBot.create(:job_template, :job_category => 'trying_job_template_1', :name => 'unauth3', :provider_type => 'Ansible') }
204
- let(:mcollective_authorized) { FactoryBot.create(:job_template, :job_category => 'trying_job_template_1', :name => 'trying4', :provider_type => 'Mcollective') }
205
- let(:ssh_params) { { :job_template_id => trying_job_template_1.id.to_s } }
206
- let(:ansible_params) { { :job_template_id => unauthorized.id.to_s } }
207
- let(:mcollective_params) { { :job_template_id => mcollective_authorized.id.to_s } }
208
- let(:params) { { :job_invocation => providers_params }.with_indifferent_access }
209
-
210
- it 'ignores unauthorized template' do
211
- unauthorized # make sure unautorized exists
212
- refute_includes composer.selected_job_templates, unauthorized
213
- end
214
-
215
- it 'contains only authorized template specified in params' do
216
- mcollective_authorized # make sure mcollective_authorized exists
217
- assert_includes composer.selected_job_templates, trying_job_template_1
218
- assert_includes composer.selected_job_templates, mcollective_authorized
219
- refute_includes composer.selected_job_templates, trying_job_template_3
220
- end
221
- end
222
- end
223
-
224
- describe '#preselected_template_for_provider(provider_type)' do
225
- context 'none template was selected through params' do
226
- it 'returns nil' do
227
- assert_nil composer.preselected_template_for_provider('SSH')
228
- end
229
- end
230
-
231
- context 'available template was selected for a specified provider through params' do
232
- let(:ssh_params) { { :job_template_id => trying_job_template_1.id.to_s } }
233
- let(:params) { { :job_invocation => providers_params }.with_indifferent_access }
234
-
235
- it 'returns the selected template because it is available for provider' do
236
- assert_equal trying_job_template_1, composer.preselected_template_for_provider('SSH')
237
- end
238
- end
239
- end
240
-
241
- describe '#pattern template_invocations' do
242
- let(:ssh_params) do
243
- { :job_template_id => trying_job_template_1.id.to_s,
244
- :job_templates => {
245
- trying_job_template_1.id.to_s => {
246
- :input_values => { input1.id.to_s => { :value => 'value1' }, unauthorized_input1.id.to_s => { :value => 'dropped' } },
247
- },
248
- }}
249
- end
250
- let(:params) { { :job_invocation => { :providers => { :ssh => ssh_params } } }.with_indifferent_access }
251
- let(:invocations) { composer.pattern_template_invocations }
252
-
253
- it 'builds pattern template invocations based on passed params and it filters out wrong inputs' do
254
- assert_equal 1, invocations.size
255
- assert_equal 1, invocations.first.input_values.size
256
- assert_equal 'value1', invocations.first.input_values.first.value
257
- end
258
- end
259
-
260
- describe '#effective_user' do
261
- let(:ssh_params) do
262
- { :job_template_id => trying_job_template_1.id.to_s,
263
- :job_templates => {
264
- trying_job_template_1.id.to_s => {
265
- :effective_user => invocation_effective_user,
266
- },
267
- }}
268
- end
269
- let(:params) { { :job_invocation => { :providers => { :ssh => ssh_params } } }.with_indifferent_access }
270
- let(:template_invocation) do
271
- trying_job_template_1.effective_user.update(:overridable => overridable, :value => 'template user')
272
- composer.pattern_template_invocations.first
273
- end
274
-
275
- context 'when overridable and provided' do
276
- let(:overridable) { true }
277
- let(:invocation_effective_user) { 'invocation user' }
278
-
279
- it 'takes the value from the template invocation' do
280
- assert_equal 'invocation user', template_invocation.effective_user
281
- end
282
- end
283
-
284
- context 'when overridable and not provided' do
285
- let(:overridable) { true }
286
- let(:invocation_effective_user) { '' }
287
-
288
- it 'takes the value from the job template' do
289
- assert_equal 'template user', template_invocation.effective_user
290
- end
291
- end
292
-
293
- context 'when not overridable and provided' do
294
- let(:overridable) { false }
295
- let(:invocation_effective_user) { 'invocation user' }
296
-
297
- it 'takes the value from the job template' do
298
- assert_equal 'template user', template_invocation.effective_user
299
- end
300
- end
301
- end
302
-
303
- describe '#displayed_search_query' do
304
- it 'is empty by default' do
305
- assert_empty composer.displayed_search_query
306
- end
307
-
308
- let(:host) { FactoryBot.create(:host) }
309
- let(:bookmark) { Bookmark.create!(:query => 'b', :name => 'bookmark', :public => true, :controller => 'hosts') }
310
-
311
- context 'all targetings parameters are present' do
312
- let(:params) { { :targeting => { :search_query => 'a', :bookmark_id => bookmark.id }, :host_ids => [ host.id ] }.with_indifferent_access }
313
-
314
- it 'explicit search query has highest priority' do
315
- assert_equal 'a', composer.displayed_search_query
316
- end
317
- end
318
-
319
- context 'host ids and bookmark are present' do
320
- let(:params) { { :targeting => { :bookmark_id => bookmark.id }, :host_ids => [ host.id ] }.with_indifferent_access }
321
-
322
- it 'hosts will be used instead of a bookmark' do
323
- assert_includes composer.displayed_search_query, host.name
324
- end
325
- end
326
-
327
- context 'bookmark is present' do
328
- let(:params) { { :targeting => { :bookmark_id => bookmark.id } }.with_indifferent_access }
329
-
330
- it 'bookmark query is used if it is available for the user' do
331
- bookmark.update_attribute :public, false
332
- assert_equal bookmark.query, composer.displayed_search_query
333
- end
334
-
335
- it 'bookmark query is used if the bookmark is public' do
336
- bookmark.owner = nil
337
- bookmark.save(:validate => false) # skip validations so owner remains nil
338
- assert_equal bookmark.query, composer.displayed_search_query
339
- end
340
-
341
- it 'empty search is returned if bookmark is not owned by the user and is not public' do
342
- bookmark.public = false
343
- bookmark.owner = nil
344
- bookmark.save(:validate => false) # skip validations so owner remains nil
345
- assert_empty composer.displayed_search_query
346
- end
347
- end
348
- end
349
-
350
- describe '#available_bookmarks' do
351
- context 'there are hostgroups and hosts bookmark' do
352
- let(:hostgroups) { Bookmark.create(:name => 'hostgroups', :query => 'name = x', :controller => 'hostgroups') }
353
- let(:hosts) { Bookmark.create(:name => 'hosts', :query => 'name = x', :controller => 'hosts') }
354
- let(:dashboard) { Bookmark.create(:name => 'dashboard', :query => 'name = x', :controller => 'dashboard') }
355
-
356
- it 'finds only host related bookmarks' do
357
- hosts
358
- dashboard
359
- hostgroups
360
- assert_includes composer.available_bookmarks, hosts
361
- assert_includes composer.available_bookmarks, dashboard
362
- refute_includes composer.available_bookmarks, hostgroups
363
- end
364
- end
365
- end
366
-
367
- describe '#targeted_hosts_count' do
368
- let(:host) { FactoryBot.create(:host) }
369
-
370
- it 'obeys authorization' do
371
- fake_scope = mock
372
- composer.stubs(:displayed_search_query => "name = #{host.name}")
373
- Host.expects(:execution_scope).returns(fake_scope)
374
- fake_scope.expects(:authorized).with(:view_hosts, Host).returns(Host.where({}))
375
- composer.targeted_hosts_count
376
- end
377
-
378
- it 'searches hosts based on displayed_search_query' do
379
- composer.stubs(:displayed_search_query => "name = #{host.name}")
380
- assert_equal 1, composer.targeted_hosts_count
381
- end
382
-
383
- it 'returns 0 for queries with syntax errors' do
384
- composer.stubs(:displayed_search_query => 'name = ')
385
- assert_equal 0, composer.targeted_hosts_count
386
- end
387
-
388
- it 'returns 0 when no query is present' do
389
- composer.stubs(:displayed_search_query => '')
390
- assert_equal 0, composer.targeted_hosts_count
391
- end
392
- end
393
-
394
- describe '#input_value_for(input)' do
395
- let(:value1) { composer.input_value_for(input1) }
396
- it 'returns new empty input value if there is no invocation' do
397
- assert value1.new_record?
398
- assert_empty value1.value
399
- end
400
-
401
- context 'there are invocations without input values for a given input' do
402
- let(:ssh_params) do
403
- { :job_template_id => trying_job_template_1.id.to_s,
404
- :job_templates => {
405
- trying_job_template_1.id.to_s => {
406
- :input_values => { },
407
- },
408
- } }
409
- end
410
- let(:params) { { :job_invocation => { :providers => { :ssh => ssh_params } } }.with_indifferent_access }
411
-
412
- it 'returns new empty input value' do
413
- assert value1.new_record?
414
- assert_empty value1.value
415
- end
416
- end
417
-
418
- context 'there are invocations with input values for a given input' do
419
- let(:ssh_params) do
420
- { :job_template_id => trying_job_template_1.id.to_s,
421
- :job_templates => {
422
- trying_job_template_1.id.to_s => {
423
- :input_values => { input1.id.to_s => { :value => 'value1' } },
424
- },
425
- } }
426
- end
427
- let(:params) { { :job_invocation => { :providers => { :ssh => ssh_params } } }.with_indifferent_access }
428
-
429
- it 'finds the value among template invocations' do
430
- assert_equal 'value1', value1.value
431
- end
432
- end
433
- end
434
-
435
- describe '#valid?' do
436
- let(:host) { FactoryBot.create(:host) }
437
- let(:ssh_params) do
438
- { :job_template_id => trying_job_template_1.id.to_s,
439
- :job_templates => {
440
- trying_job_template_1.id.to_s => {
441
- :input_values => { input1.id.to_s => { :value => 'value1' } },
442
- },
443
- } }
444
- end
445
-
446
- let(:params) do
447
- { :job_invocation => { :providers => { :ssh => ssh_params } }, :targeting => { :search_query => "name = #{host.name}" } }.with_indifferent_access
448
- end
449
-
450
- it 'validates all associated objects even if some of the is invalid' do
451
- composer
452
- composer.job_invocation.expects(:valid?).returns(false)
453
- composer.targeting.expects(:valid?).returns(false)
454
- composer.pattern_template_invocations.each { |invocation| invocation.expects(:valid?).returns(false) }
455
- assert_not composer.valid?
456
- end
457
- end
458
-
459
- describe 'concurrency control' do
460
-
461
- describe 'with concurrency control set' do
462
- let(:params) do
463
- { :job_invocation => { :providers => { :ssh => ssh_params }, :concurrency_level => '5' } }.with_indifferent_access
464
- end
465
-
466
- it 'accepts the concurrency options' do
467
- assert_equal 5, composer.job_invocation.concurrency_level
468
- end
469
- end
470
-
471
- it 'can be disabled' do
472
- assert_nil composer.job_invocation.concurrency_level
473
- end
474
- end
475
-
476
- describe 'triggering' do
477
- let(:params) do
478
- { :job_invocation => { :providers => { :ssh => ssh_params } }, :triggering => { :mode => 'future', :end_time=> {"end_time(3i)": 1, "end_time(2i)": 2, "end_time(1i)": 3, "end_time(4i)": 4, "end_time(5i)": 5} }}.with_indifferent_access
479
- end
480
-
481
- it 'accepts the triggering params' do
482
- assert_equal :future, composer.job_invocation.triggering.mode
483
- end
484
-
485
- it 'formats the triggering end time when its unordered' do
486
- assert_equal Time.local(3,2,1,4,5), composer.job_invocation.triggering.end_time
487
- end
488
- end
489
-
490
- describe '#save' do
491
- it 'triggers save on job_invocation if it is valid' do
492
- composer.stubs(:valid? => true)
493
- composer.job_invocation.expects(:save)
494
- composer.save
495
- end
496
-
497
- it 'does not trigger save on job_invocation if it is invalid' do
498
- composer.stubs(:valid? => false)
499
- composer.job_invocation.expects(:save).never
500
- composer.save
501
- end
502
- end
503
-
504
- describe '#job_category' do
505
- it 'triggers job_category on job_invocation' do
506
- composer
507
- composer.job_invocation.expects(:job_category)
508
- composer.job_category
509
- end
510
- end
511
-
512
- describe '#password' do
513
- let(:password) { 'changeme' }
514
- let(:params) do
515
- { :job_invocation => { :password => password }}.with_indifferent_access
516
- end
517
-
518
- it 'sets the password properly' do
519
- composer
520
- assert_equal password, composer.job_invocation.password
521
- end
522
- end
523
-
524
- describe '#key_passphrase' do
525
- let(:key_passphrase) { 'changeme' }
526
- let(:params) do
527
- { :job_invocation => { :key_passphrase => key_passphrase }}
528
- end
529
-
530
- it 'sets the key passphrase properly' do
531
- composer
532
- assert_equal key_passphrase, composer.job_invocation.key_passphrase
533
- end
534
- end
535
-
536
- describe '#effective_user_password' do
537
- let(:effective_user_password) { 'password' }
538
- let(:params) do
539
- { :job_invocation => { :effective_user_password => effective_user_password }}
540
- end
541
-
542
- it 'sets the effective_user_password password properly' do
543
- composer
544
- assert_equal effective_user_password, composer.job_invocation.effective_user_password
545
- end
546
- end
547
-
548
- describe '#targeting' do
549
- it 'triggers targeting on job_invocation' do
550
- composer
551
- composer.job_invocation.expects(:targeting)
552
- composer.targeting
553
- end
554
- end
555
-
556
- describe '#compose_from_invocation(existing_invocation)' do
557
- let(:host) { FactoryBot.create(:host) }
558
- let(:ssh_params) do
559
- { :job_template_id => trying_job_template_1.id.to_s,
560
- :job_templates => {
561
- trying_job_template_1.id.to_s => {
562
- :input_values => { input1.id.to_s => { :value => 'value1' } },
563
- },
564
- } }
565
- end
566
- let(:params) do
567
- {
568
- :job_invocation => {
569
- :providers => { :ssh => ssh_params },
570
- :concurrency_level => 5,
571
- },
572
- :targeting => {
573
- :search_query => "name = #{host.name}",
574
- :targeting_type => Targeting::STATIC_TYPE,
575
- },
576
- }.with_indifferent_access
577
- end
578
- let(:existing) { composer.job_invocation }
579
- let(:new_composer) { JobInvocationComposer.from_job_invocation(composer.job_invocation) }
580
-
581
- before do
582
- composer.save
583
- end
584
-
585
- it 'sets the same job name' do
586
- assert_equal existing.job_category, new_composer.job_category
587
- end
588
-
589
- it 'accepts additional host ids' do
590
- new_composer = JobInvocationComposer.from_job_invocation(composer.job_invocation, { :host_ids => [host.id] })
591
- assert_equal "name ^ (#{host.name})", new_composer.search_query
592
- end
593
-
594
- it 'builds new targeting object which keeps search query' do
595
- refute_equal existing.targeting, new_composer.targeting
596
- assert_equal existing.targeting.search_query, new_composer.search_query
597
- end
598
-
599
- it 'keeps job template ids' do
600
- assert_equal existing.pattern_template_invocations.map(&:template_id), new_composer.job_template_ids
601
- end
602
-
603
- it 'keeps template invocations and their values' do
604
- assert_equal existing.pattern_template_invocations.size, new_composer.pattern_template_invocations.size
605
- end
606
-
607
- it 'sets the same concurrency control options' do
608
- assert_equal existing.concurrency_level, new_composer.job_invocation.concurrency_level
609
- end
610
-
611
- end
612
- end
613
- end
614
-
615
- describe '.from_api_params' do
616
- let(:composer) do
617
- JobInvocationComposer.from_api_params(params)
618
- end
619
- let(:bookmark) { bookmarks(:one) }
620
-
621
- context 'with targeting from bookmark' do
622
-
623
- before do
624
- [trying_job_template_1, trying_job_template_3] # mentioning templates we want to have initialized in the test
625
- end
626
-
627
- let(:params) do
628
- { :job_category => trying_job_template_1.job_category,
629
- :job_template_id => trying_job_template_1.id,
630
- :targeting_type => 'static_query',
631
- :bookmark_id => bookmark.id }
632
- end
633
-
634
- it 'creates invocation with a bookmark' do
635
- assert composer.save!
636
- assert_equal bookmark, composer.job_invocation.targeting.bookmark
637
- assert_equal composer.job_invocation.targeting.user, User.current
638
- assert_not_empty composer.job_invocation.pattern_template_invocations
639
- end
640
- end
641
-
642
- context 'with targeting from search query' do
643
- let(:params) do
644
- { :job_category => trying_job_template_1.job_category,
645
- :job_template_id => trying_job_template_1.id,
646
- :targeting_type => 'static_query',
647
- :search_query => 'some hosts' }
648
- end
649
-
650
- it 'creates invocation with a search query' do
651
- assert composer.save!
652
- assert_equal 'some hosts', composer.job_invocation.targeting.search_query
653
- assert_not_empty composer.job_invocation.pattern_template_invocations
654
- end
655
- end
656
-
657
- context 'with with inputs' do
658
- let(:params) do
659
- { :job_category => trying_job_template_5.job_category,
660
- :job_template_id => trying_job_template_5.id,
661
- :targeting_type => 'static_query',
662
- :search_query => 'some hosts',
663
- :inputs => {input5.name => 'some_value'}}
664
- end
665
-
666
- it 'finds the inputs by name' do
667
- assert composer.save!
668
- values = composer.pattern_template_invocations.first.input_values
669
- assert_equal 1, values.count
670
- assert_equal 'some_value', values.first.value
671
- end
672
-
673
- it 'can be forced to be empty' do
674
- params[:inputs] = {input5.name => ''}
675
- assert composer.save!
676
- values = composer.pattern_template_invocations.first.input_values
677
- assert_equal 1, values.count
678
- assert_equal '', values.first.value
679
- end
680
- end
681
-
682
- context 'with inputs and default values' do
683
- let(:params) do
684
- { :job_category => trying_job_template_5.job_category,
685
- :job_template_id => trying_job_template_5.id,
686
- :targeting_type => 'static_query',
687
- :search_query => 'some hosts',
688
- :inputs => {}}
689
- end
690
-
691
- it 'uses the default input values' do
692
- input5 # Force the factory to be materialized
693
- assert composer.save!
694
- assert_equal 1, composer.pattern_template_invocations.first.input_values.collect.count
695
- end
696
- end
697
-
698
- context 'with provider inputs' do
699
- let(:params) do
700
- { :job_category => provider_inputs_job_template.job_category,
701
- :job_template_id => provider_inputs_job_template.id,
702
- :targeting_type => 'static_query',
703
- :search_query => 'some hosts',
704
- :inputs => { input4.name => 'some_value' },
705
- :ansible => { 'tags' => 'bar', 'tags_flag' => '--skip-tags' } }
706
- end
707
-
708
- it 'detects provider inputs' do
709
- assert composer.save!
710
- scope = composer.job_invocation.pattern_template_invocations.first.provider_input_values
711
- tags = scope.find_by :name => 'tags'
712
- flags = scope.find_by :name => 'tags_flag'
713
- assert_equal 'bar', tags.value
714
- assert_equal '--skip-tags', flags.value
715
- end
716
- end
717
-
718
- context 'with effective user' do
719
- let(:params) do
720
- { :job_category => trying_job_template_1.job_category,
721
- :job_template_id => trying_job_template_1.id,
722
- :effective_user => 'invocation user',
723
- :targeting_type => 'static_query',
724
- :search_query => 'some hosts',
725
- :inputs => {input1.name => 'some_value'}}
726
- end
727
-
728
- let(:template_invocation) { composer.job_invocation.pattern_template_invocations.first }
729
-
730
- it 'sets the effective user based on the input' do
731
- assert composer.save!
732
- assert_equal 'invocation user', template_invocation.effective_user
733
- end
734
- end
735
-
736
- context 'with concurrency_control' do
737
- let(:level) { 5 }
738
- let(:params) do
739
- { :job_category => trying_job_template_1.job_category,
740
- :job_template_id => trying_job_template_1.id,
741
- :concurrency_control => {
742
- :concurrency_level => level,
743
- },
744
- :targeting_type => 'static_query',
745
- :search_query => 'some hosts',
746
- :inputs => { input1.name => 'some_value' } }
747
- end
748
-
749
- it 'sets the concurrency level based on the input' do
750
- assert composer.save!
751
- assert_equal level, composer.job_invocation.concurrency_level
752
- end
753
- end
754
-
755
- context 'with rex feature defined' do
756
- let(:feature) { FactoryBot.create(:remote_execution_feature) }
757
- let(:params) do
758
- { :job_category => trying_job_template_1.job_category,
759
- :job_template_id => trying_job_template_1.id,
760
- :remote_execution_feature_id => feature.id,
761
- :targeting_type => 'static_query',
762
- :search_query => 'some hosts',
763
- :inputs => { input1.name => 'some_value' } }
764
- end
765
-
766
- it 'sets the remote execution feature based on the input' do
767
- assert composer.save!
768
- assert_equal feature, composer.job_invocation.remote_execution_feature
769
- end
770
-
771
- it 'sets the remote execution_feature id based on `feature` param' do
772
- params[:remote_execution_feature_id] = nil
773
- params[:feature] = feature.label
774
- params[:job_template_id] = trying_job_template_1.id
775
- refute_equal feature.job_template, trying_job_template_1
776
-
777
- assert composer.save!
778
- assert_equal feature, composer.job_invocation.remote_execution_feature
779
- end
780
- end
781
-
782
- context 'with invalid targeting' do
783
- let(:params) do
784
- { :job_category => trying_job_template_1.job_category,
785
- :job_template_id => trying_job_template_1.id,
786
- :targeting_type => 'fake',
787
- :search_query => 'some hosts',
788
- :inputs => {input1.name => 'some_value'}}
789
- end
790
-
791
- it 'handles errors' do
792
- assert_raises(ActiveRecord::RecordNotSaved) do
793
- composer.save!
794
- end
795
- end
796
- end
797
-
798
- context 'with invalid bookmark and search query' do
799
- let(:params) do
800
- { :job_category => trying_job_template_1.job_category,
801
- :job_template_id => trying_job_template_1.id,
802
- :targeting_type => 'static_query',
803
- :search_query => 'some hosts',
804
- :bookmark_id => bookmark.id,
805
- :inputs => {input1.name => 'some_value'}}
806
- end
807
-
808
- it 'handles errors' do
809
- assert_raises(Foreman::Exception) do
810
- JobInvocationComposer.from_api_params(params)
811
- end
812
- end
813
- end
814
-
815
- context 'with invalid inputs' do
816
- let(:params) do
817
- { :job_category => trying_job_template_1.job_category,
818
- :job_template_id => trying_job_template_1.id,
819
- :targeting_type => 'static_query',
820
- :search_query => 'some hosts',
821
- :inputs => {input3.name => nil}}
822
- end
823
-
824
- it 'handles errors' do
825
- error = assert_raises(ActiveRecord::RecordNotSaved) do
826
- composer.save!
827
- end
828
- assert_includes error.message, "Template #{trying_job_template_1.name}: Input #{input3.name.downcase}: Value can't be blank"
829
- end
830
- end
831
-
832
- context 'with empty values for non-required inputs' do
833
- let(:params) do
834
- { :job_category => trying_job_template_1.job_category,
835
- :job_template_id => trying_job_template_1.id,
836
- :targeting_type => 'static_query',
837
- :search_query => 'some hosts',
838
- :inputs => {input3.name => 'some value'}}
839
- end
840
-
841
- it 'accepts the params' do
842
- composer.save!
843
- assert_not composer.job_invocation.new_record?
844
- end
845
- end
846
-
847
- context 'with missing required inputs' do
848
- let(:params) do
849
- { :job_category => trying_job_template_1.job_category,
850
- :job_template_id => trying_job_template_1.id,
851
- :targeting_type => 'static_query',
852
- :search_query => 'some hosts',
853
- :inputs => {input1.name => 'some_value'}}
854
- end
855
-
856
- it 'handles errors' do
857
- assert_predicate input3, :required
858
-
859
- error = assert_raises(ActiveRecord::RecordNotSaved) do
860
- composer.save!
861
- end
862
-
863
- assert_includes error.message, "Template #{trying_job_template_1.name}: Not all required inputs have values. Missing inputs: #{input3.name}"
864
- end
865
- end
866
- end
867
-
868
- describe '#from_job_invocation' do
869
- let(:job_invocation) do
870
- as_admin { FactoryBot.create(:job_invocation, :with_template, :with_task) }
871
- end
872
-
873
- before do
874
- job_invocation.targeting.host_ids = job_invocation.template_invocations_host_ids
875
- end
876
-
877
- it 'marks targeting as resolved if static' do
878
- created = JobInvocationComposer.from_job_invocation(job_invocation).job_invocation
879
- assert created.targeting.resolved?
880
- created.targeting.save
881
- created.targeting.reload
882
- assert_equal job_invocation.template_invocations_host_ids, created.targeting.targeting_hosts.pluck(:host_id)
883
- end
884
-
885
- it 'takes randomized_ordering from the original job invocation when rerunning failed' do
886
- job_invocation.targeting.randomized_ordering = true
887
- job_invocation.targeting.save!
888
- host_ids = job_invocation.targeting.hosts.pluck(:id)
889
- composer = JobInvocationComposer.from_job_invocation(job_invocation, :host_ids => host_ids)
890
- assert composer.job_invocation.targeting.randomized_ordering
891
- end
892
-
893
- it 'works with invalid hosts' do
894
- host = job_invocation.targeting.hosts.first
895
- ::Host::Managed.any_instance.stubs(:valid?).returns(false)
896
- composer = JobInvocationComposer.from_job_invocation(job_invocation, {})
897
- targeting = composer.compose.job_invocation.targeting
898
- targeting.save!
899
- targeting.reload
900
- assert targeting.valid?
901
- assert_equal targeting.hosts.pluck(:id), [host.id]
902
- end
903
- end
904
-
905
- describe '.for_feature' do
906
- let(:feature) { FactoryBot.create(:remote_execution_feature, job_template: trying_job_template_1) }
907
- let(:host) { FactoryBot.create(:host) }
908
- let(:bookmark) { Bookmark.create!(:query => 'b', :name => 'bookmark', :public => true, :controller => 'hosts') }
909
-
910
- context 'specifying hosts' do
911
- it 'takes a bookmarked search' do
912
- composer = JobInvocationComposer.for_feature(feature.label, bookmark, {})
913
- assert_equal bookmark.id, composer.params['targeting']['bookmark_id']
914
- end
915
-
916
- it 'takes an array of host ids' do
917
- composer = JobInvocationComposer.for_feature(feature.label, [host.id], {})
918
- assert_match(/#{host.name}/, composer.params['targeting']['search_query'])
919
- end
920
-
921
- it 'takes a single host object' do
922
- composer = JobInvocationComposer.for_feature(feature.label, host, {})
923
- assert_match(/#{host.name}/, composer.params['targeting']['search_query'])
924
- end
925
-
926
- it 'takes an array of host FQDNs' do
927
- composer = JobInvocationComposer.for_feature(feature.label, [host.fqdn], {})
928
- assert_match(/#{host.name}/, composer.params['targeting']['search_query'])
929
- end
930
-
931
- it 'takes a search query string' do
932
- composer = JobInvocationComposer.for_feature(feature.label, 'host.example.com', {})
933
- assert_equal 'host.example.com', composer.search_query
934
- end
935
- end
936
- end
937
-
938
- describe '#resolve_job_category and #resolve job_templates' do
939
- let(:setting_template) { as_admin { FactoryBot.create(:job_template, :name => 'trying setting', :job_category => 'fluff') } }
940
- let(:other_template) { as_admin { FactoryBot.create(:job_template, :name => 'trying something', :job_category => 'fluff') } }
941
- let(:second_template) { as_admin { FactoryBot.create(:job_template, :name => 'second template', :job_category => 'fluff') } }
942
- let(:params) { { :host_ids => nil, :targeting => { :targeting_type => "static_query", :bookmark_id => nil }, :job_template_id => setting_template.id } }
943
- let(:composer) { JobInvocationComposer.from_api_params(params) }
944
-
945
- context 'with template in setting present' do
946
- before do
947
- Setting[:remote_execution_form_job_template] = setting_template.name
948
- end
949
-
950
- it 'should resolve category to the setting value' do
951
- assert_equal setting_template.job_category, composer.resolve_job_category('foo')
952
- end
953
-
954
- it 'should resolve template to the setting value' do
955
- assert_equal setting_template, composer.resolve_job_template([other_template, setting_template])
956
- end
957
-
958
- it 'should respect provider templates when resolving templates' do
959
- assert_equal other_template, composer.resolve_job_template([other_template])
960
- end
961
- end
962
-
963
- context 'with template in setting absent' do
964
- it 'should resolve category to the default value' do
965
- category = 'foo'
966
- assert_equal category, composer.resolve_job_category(category)
967
- end
968
-
969
- it 'should resolve template to the first in category' do
970
- assert_equal other_template, composer.resolve_job_template([other_template, second_template])
971
- end
972
- end
973
- end
974
- end