foreman_remote_execution 14.1.0 → 14.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/job_template.rb +1 -0
  3. data/app/views/template_invocations/show.js.erb +1 -1
  4. data/db/migrate/20240312133027_extend_template_invocation_events.rb +9 -0
  5. data/lib/foreman_remote_execution/engine.rb +1 -1
  6. data/lib/foreman_remote_execution/version.rb +1 -1
  7. data/lib/tasks/foreman_remote_execution_tasks.rake +3 -0
  8. data/test/benchmark/run_hosts_job_benchmark.rb +70 -0
  9. data/test/benchmark/targeting_benchmark.rb +31 -0
  10. data/test/factories/foreman_remote_execution_factories.rb +147 -0
  11. data/test/functional/api/v2/foreign_input_sets_controller_test.rb +58 -0
  12. data/test/functional/api/v2/job_invocations_controller_test.rb +446 -0
  13. data/test/functional/api/v2/job_templates_controller_test.rb +110 -0
  14. data/test/functional/api/v2/registration_controller_test.rb +73 -0
  15. data/test/functional/api/v2/remote_execution_features_controller_test.rb +34 -0
  16. data/test/functional/api/v2/template_invocations_controller_test.rb +33 -0
  17. data/test/functional/cockpit_controller_test.rb +16 -0
  18. data/test/functional/job_invocations_controller_test.rb +132 -0
  19. data/test/functional/job_templates_controller_test.rb +31 -0
  20. data/test/functional/ui_job_wizard_controller_test.rb +16 -0
  21. data/test/graphql/mutations/job_invocations/create_test.rb +58 -0
  22. data/test/graphql/queries/job_invocation_query_test.rb +31 -0
  23. data/test/graphql/queries/job_invocations_query_test.rb +35 -0
  24. data/test/helpers/remote_execution_helper_test.rb +46 -0
  25. data/test/support/remote_execution_helper.rb +5 -0
  26. data/test/test_plugin_helper.rb +9 -0
  27. data/test/unit/actions/run_host_job_test.rb +115 -0
  28. data/test/unit/actions/run_hosts_job_test.rb +214 -0
  29. data/test/unit/api_params_test.rb +25 -0
  30. data/test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb +29 -0
  31. data/test/unit/concerns/host_extensions_test.rb +219 -0
  32. data/test/unit/concerns/nic_extensions_test.rb +9 -0
  33. data/test/unit/execution_task_status_mapper_test.rb +92 -0
  34. data/test/unit/input_template_renderer_test.rb +503 -0
  35. data/test/unit/job_invocation_composer_test.rb +974 -0
  36. data/test/unit/job_invocation_report_template_test.rb +60 -0
  37. data/test/unit/job_invocation_test.rb +232 -0
  38. data/test/unit/job_template_effective_user_test.rb +37 -0
  39. data/test/unit/job_template_test.rb +316 -0
  40. data/test/unit/remote_execution_feature_test.rb +86 -0
  41. data/test/unit/remote_execution_provider_test.rb +298 -0
  42. data/test/unit/renderer_scope_input_test.rb +49 -0
  43. data/test/unit/targeting_test.rb +206 -0
  44. data/test/unit/template_invocation_input_value_test.rb +38 -0
  45. metadata +39 -2
@@ -0,0 +1,503 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class InputTemplateRendererTest < ActiveSupport::TestCase
4
+ let(:host) { FactoryBot.build(:host) }
5
+
6
+ context 'renderer for simple template without inputs' do
7
+ let(:renderer) { InputTemplateRenderer.new(FactoryBot.build(:job_template, :template => 'id <%= preview? %>')) }
8
+
9
+ it 'should render the content' do
10
+ assert_equal 'id false', renderer.render
11
+ end
12
+
13
+ it 'should render preview' do
14
+ assert_equal 'id true', renderer.preview
15
+ end
16
+
17
+ it 'should allow accessing current_user' do
18
+ setup_user(:view_job_templates)
19
+ renderer = InputTemplateRenderer.new(FactoryBot.build(:job_template, :template => "They call me '<%= current_user %>'"))
20
+ assert_equal "They call me '#{User.current.login}'", renderer.preview
21
+ end
22
+ end
23
+
24
+ context 'renderer for template with user input used' do
25
+ let(:template) { FactoryBot.build(:job_template, :template => 'service restart <%= input("service_name") -%>') }
26
+ let(:renderer) { InputTemplateRenderer.new(template) }
27
+
28
+ context 'but without input defined' do
29
+ describe 'rendering' do
30
+ let(:result) { renderer.render }
31
+ it 'should return false' do
32
+ refute result
33
+ end
34
+
35
+ it 'should register an error' do
36
+ result # let is lazy
37
+ assert_not_nil renderer.error_message
38
+ assert_not_empty renderer.error_message
39
+ end
40
+ end
41
+
42
+ describe 'preview' do
43
+ let(:result) { renderer.preview }
44
+ it 'should return false' do
45
+ refute result
46
+ end
47
+
48
+ it 'should register an error' do
49
+ result # let is lazy
50
+ assert_not_nil renderer.error_message
51
+ assert_not_empty renderer.error_message
52
+ end
53
+ end
54
+ end
55
+
56
+ context 'with matching input defined' do
57
+
58
+ let(:job_invocation) { FactoryBot.create(:job_invocation) }
59
+ let(:template_invocation) { FactoryBot.build(:template_invocation, :template => template) }
60
+ let(:result) { renderer.render }
61
+
62
+ before do
63
+ template.template_inputs << FactoryBot.build(:template_input, :name => 'service_name', :input_type => 'user')
64
+ job_invocation.template_invocations << template_invocation
65
+ end
66
+
67
+ describe 'rendering' do
68
+ it 'can preview' do
69
+ assert_equal 'service restart $USER_INPUT[service_name]', renderer.preview
70
+ end
71
+
72
+ context 'with invocation specified and a required input' do
73
+ before do
74
+ template.template_inputs.first.update(:required => true)
75
+ template_invocation.reload
76
+ renderer.invocation = template_invocation
77
+ end
78
+
79
+ it 'cannot render the content' do
80
+ assert_not result
81
+ refute_nil renderer.error_message
82
+ refute_empty renderer.error_message
83
+ end
84
+ end
85
+
86
+ context 'with invocation specified' do
87
+ before do
88
+ FactoryBot.create(:template_invocation_input_value,
89
+ :template_invocation => template_invocation,
90
+ :template_input => template.template_inputs.first,
91
+ :value => 'foreman')
92
+ template_invocation.reload # need to get input_values findable
93
+ renderer.invocation = template_invocation
94
+ end
95
+
96
+ it 'can render with job invocation with corresponding value' do
97
+ assert_equal 'service restart foreman', renderer.render
98
+ end
99
+ end
100
+
101
+ it 'renders even without an input value' do
102
+ renderer.invocation = template_invocation
103
+ assert_equal 'service restart ', renderer.render
104
+ end
105
+
106
+ describe 'with circular reference' do
107
+ let(:recursive_template_with_inputs) do
108
+ FactoryBot.create(:job_template, :name => 'test', :template => 'test')
109
+ end
110
+
111
+ let(:template_with_inputs) do
112
+ FactoryBot.create(:job_template, :template => 'test').tap do |template|
113
+ template.foreign_input_sets << FactoryBot.build(:foreign_input_set, :target_template => recursive_template_with_inputs)
114
+ end
115
+ end
116
+
117
+ let(:recursive_template_without_inputs) do
118
+ FactoryBot.create(:job_template, :name => 'recursive template', :template => '<%= render_template("template with inputs", "action" => "install") %>')
119
+ end
120
+
121
+ let(:template_without_inputs) do
122
+ FactoryBot.create(:job_template, :name => 'template with inputs', :template => "<%= render_template('#{recursive_template_without_inputs.name}') %>")
123
+ end
124
+
125
+ before do
126
+ User.current = users :admin
127
+ end
128
+
129
+ it 'handles circular references in templates' do
130
+ renderer.invocation = FactoryBot.build(:template_invocation, :template => template_without_inputs)
131
+ renderer.template = template_without_inputs
132
+ assert_not renderer.render
133
+ assert_includes renderer.error_message, 'Recursive rendering of templates detected'
134
+ end
135
+
136
+ it 'handles circular references in inputs' do
137
+ assert_raises(ActiveRecord::RecordInvalid) do
138
+ input_set = FactoryBot.build(:foreign_input_set, :target_template => template_with_inputs, :include_all => false,
139
+ :include => 'package, debug', :exclude => 'action,debug')
140
+ recursive_template_with_inputs.foreign_input_sets << input_set
141
+ recursive_template_with_inputs.save!
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ context 'renderer for template with input set and render_template' do
149
+ let(:command_template) do
150
+ FactoryBot.build(:job_template, :name => 'command action', :template => '<%= input("command") -%>').tap do |template|
151
+ template.template_inputs << FactoryBot.build(:template_input, :name => 'command', :input_type => 'user')
152
+ template.template_inputs << FactoryBot.build(:template_input, :name => 'debug', :input_type => 'user')
153
+ end
154
+ end
155
+
156
+ let(:package_template) do
157
+ FactoryBot.build(:job_template, :name => 'package action', :template => <<-TEMPLATE.strip_heredoc) do |template|
158
+ <%= render_template("command action", "command" => "yum -y \#{ input("action") } \#{ input('package') }") -%>
159
+ TEMPLATE
160
+ template.template_inputs << FactoryBot.build(:template_input, :name => 'package', :input_type => 'user')
161
+ template.template_inputs << FactoryBot.build(:template_input, :name => 'action', :input_type => 'user')
162
+ template.foreign_input_sets << FactoryBot.build(:foreign_input_set, :target_template => command_template, :include_all => true, :exclude => 'command')
163
+ end
164
+ end
165
+
166
+ let(:template) do
167
+ FactoryBot.create(:job_template,
168
+ :template => '<%= render_template("package action", { :action => "install" }, { :with_foreign_input_set => true }) %>').tap do |template|
169
+ template.foreign_input_sets << FactoryBot.build(:foreign_input_set, :target_template => package_template, :include_all => true, :exclude => 'action')
170
+ end
171
+ end
172
+
173
+ let(:job_invocation) { FactoryBot.create(:job_invocation) }
174
+ let(:template_invocation) { FactoryBot.build(:template_invocation, :template => template) }
175
+ let(:renderer) { InputTemplateRenderer.new(template) }
176
+ let(:result) { renderer.render }
177
+
178
+ before do
179
+ User.current = users :admin
180
+ command_template.save!
181
+ package_template.save!
182
+ job_invocation.template_invocations << template_invocation
183
+ end
184
+
185
+ describe 'foreign input set' do
186
+ describe 'with include_all' do
187
+ let(:template) do
188
+ FactoryBot.create(:job_template, :template => '<%= render_template("package action", "action" => "install") %>').tap do |template|
189
+ template.foreign_input_sets << FactoryBot.build(:foreign_input_set, :target_template => package_template, :include_all => true)
190
+ end
191
+ end
192
+
193
+ let(:template_2) do
194
+ FactoryBot.create(:job_template, :template => '<%= render_template("package action", "action" => "install") %>').tap do |template|
195
+ template.foreign_input_sets << FactoryBot.build(:foreign_input_set,
196
+ :target_template => package_template, :include_all => true, :include => '', :exclude => '')
197
+ end
198
+ end
199
+
200
+ it 'includes all inputs from the imported template' do
201
+ assert_equal ['action', 'debug', 'package'], template.template_inputs_with_foreign.map(&:name).sort
202
+ assert_equal ['action', 'debug', 'package'], template_2.template_inputs_with_foreign.map(&:name).sort
203
+ end
204
+ end
205
+
206
+ describe 'with include_all and some excludes' do
207
+ let(:template) do
208
+ FactoryBot.create(:job_template, :template => '<%= render_template("package action", "action" => "install") %>').tap do |template|
209
+ template.foreign_input_sets << FactoryBot.build(:foreign_input_set, :target_template => package_template, :include_all => true, :exclude => 'action,debug')
210
+ end
211
+ end
212
+
213
+ it 'includes all inputs from the imported template except the listed once' do
214
+ assert_equal ['package'], template.template_inputs_with_foreign.map(&:name).sort
215
+ end
216
+ end
217
+
218
+ describe 'with some includes and some excludes' do
219
+ let(:template) do
220
+ FactoryBot.create(:job_template, :template => '<%= render_template("package action", "action" => "install") %>').tap do |template|
221
+ template.foreign_input_sets << FactoryBot.build(:foreign_input_set, :target_template => package_template, :include_all => false,
222
+ :include => 'package, debug', :exclude => 'action,debug')
223
+ end
224
+ end
225
+
226
+ it 'includes all inputs from the imported template' do
227
+ assert_equal ['package'], template.template_inputs_with_foreign.map(&:name).sort
228
+ end
229
+ end
230
+ end
231
+
232
+ context 'with invocation specified' do
233
+ before do
234
+ FactoryBot.create(:template_invocation_input_value,
235
+ :template_invocation => template_invocation,
236
+ :template_input => template.template_inputs_with_foreign.find { |input| input.name == 'package' },
237
+ :value => 'zsh')
238
+ renderer.invocation = template_invocation
239
+ renderer.invocation.reload
240
+ end
241
+
242
+ it 'can render with job invocation with corresponding value' do
243
+ rendered = renderer.render
244
+ assert_nil renderer.error_message
245
+ assert_equal 'yum -y install zsh', rendered
246
+ end
247
+ end
248
+
249
+ context 'with explicitly specifying inputs' do
250
+ let(:template) do
251
+ FactoryBot.create(:job_template,
252
+ :template => '<%= render_template("package action", {"action" => "install", :package => "zsh"}) %>')
253
+ end
254
+
255
+ before do
256
+ template_invocation.reload
257
+ renderer.invocation = template_invocation
258
+ end
259
+
260
+ it 'can render with job invocation with corresponding value' do
261
+ rendered = renderer.render
262
+ assert_nil renderer.error_message
263
+ assert_equal 'yum -y install zsh', rendered
264
+ end
265
+ end
266
+
267
+ it 'renders even without an input value' do
268
+ renderer.invocation = template_invocation
269
+ rendered = renderer.render
270
+ assert_nil renderer.error_message
271
+ assert_equal 'yum -y install ', rendered
272
+ end
273
+ end
274
+
275
+ context 'with options specified' do
276
+ let(:job_invocation) { FactoryBot.create(:job_invocation) }
277
+ let(:template_invocation) { FactoryBot.build(:template_invocation, :template => template) }
278
+ let(:result) { renderer.render }
279
+ let(:required) { false }
280
+ let(:input) do
281
+ FactoryBot.create(:template_invocation_input_value,
282
+ :template_invocation => template_invocation,
283
+ :template_input => template.template_inputs.first,
284
+ :value => 'foreman')
285
+ end
286
+
287
+ before do
288
+ template.template_inputs << FactoryBot.build(:template_input, :name => 'service_name', :input_type => 'user', :options => "httpd\nforeman", :required => required)
289
+ job_invocation.template_invocations << template_invocation
290
+ input
291
+ template_invocation.reload
292
+ renderer.invocation = template_invocation
293
+ end
294
+
295
+ context 'with a valid input defined' do
296
+ context 'with an optional input' do
297
+ it 'can render with job invocation with corresponding value' do
298
+ assert_equal 'service restart foreman', result
299
+ end
300
+ end
301
+
302
+ context 'with required input' do
303
+ let(:required) { true }
304
+
305
+ it 'renders the template when the input is provided' do
306
+ assert_equal 'service restart foreman', result
307
+ end
308
+ end
309
+ end
310
+
311
+ context 'without provided input' do
312
+ let(:input) { nil }
313
+
314
+ context 'with optional input' do
315
+ it 'renders the template' do
316
+ assert_equal 'service restart ', result
317
+ end
318
+ end
319
+
320
+ context 'with required input' do
321
+ let(:required) { true }
322
+
323
+ it 'renders the template' do
324
+ result
325
+ assert_match(/Value for required input '.*' was not specified/, renderer.error_message)
326
+ end
327
+ end
328
+ end
329
+ end
330
+ end
331
+
332
+ context 'renderer for template with fact input used' do
333
+ let(:template) { FactoryBot.build(:job_template, :template => 'echo <%= input("issue") -%> > /etc/issue') }
334
+ let(:renderer) { InputTemplateRenderer.new(template) }
335
+
336
+ context 'with matching input defined' do
337
+ before { renderer.template.template_inputs<< FactoryBot.build(:template_input, :name => 'issue', :input_type => 'fact', :fact_name => 'issue', :required => true) }
338
+ let(:result) { renderer.render }
339
+
340
+ describe 'rendering' do
341
+ it 'can\'t render the content without host since we don\'t have facts' do
342
+ assert_not result
343
+ end
344
+
345
+ it 'registers an error' do
346
+ result # let is lazy
347
+ assert_not_nil renderer.error_message
348
+ assert_not_empty renderer.error_message
349
+ end
350
+
351
+ context 'with host specified' do
352
+ before { renderer.host = FactoryBot.create(:host) }
353
+
354
+ describe 'rendering' do
355
+ it 'can\'t render the content without host since we don\'t have fact value' do
356
+ assert_not result
357
+ end
358
+
359
+ it 'registers an error' do
360
+ result # let is lazy
361
+ assert_not_nil renderer.error_message
362
+ assert_not_empty renderer.error_message
363
+ end
364
+ end
365
+
366
+ describe 'preview' do
367
+ it 'should render preview' do
368
+ assert_equal 'echo $FACT_INPUT[issue] > /etc/issue', renderer.preview
369
+ end
370
+ end
371
+
372
+ context 'with existing fact' do
373
+ let(:fact) { FactoryBot.create(:fact_name, :name => 'issue') }
374
+
375
+ describe 'rendering' do
376
+ it 'can\'t render the content without host since we don\'t have fact value' do
377
+ fact # let is lazy
378
+ assert_not result
379
+ end
380
+
381
+ it 'registers an error' do
382
+ result # let is lazy
383
+ assert_not_nil renderer.error_message
384
+ assert_not_empty renderer.error_message
385
+ end
386
+ end
387
+
388
+ describe 'preview' do
389
+ it 'should render preview' do
390
+ assert_equal 'echo $FACT_INPUT[issue] > /etc/issue', renderer.preview
391
+ end
392
+ end
393
+
394
+ context 'with fact issue value' do
395
+ before { FactoryBot.create(:fact_value, :host => renderer.host, :fact_name => fact, :value => 'banner') }
396
+
397
+ let(:result) { renderer.render }
398
+
399
+ it 'can render with job invocation with corresponding value' do
400
+ assert_equal 'echo banner > /etc/issue', result
401
+ end
402
+ end
403
+ end
404
+ end
405
+ end
406
+
407
+ describe 'preview' do
408
+ it 'should render preview' do
409
+ assert_equal 'echo $FACT_INPUT[issue] > /etc/issue', renderer.preview
410
+ end
411
+
412
+ context 'with host specified' do
413
+ before do
414
+ host = FactoryBot.create(:host)
415
+ fact = FactoryBot.create(:fact_name, :name => 'issue')
416
+ FactoryBot.create(:fact_value, :host => host, :fact_name => fact, :value => 'banner')
417
+ renderer.host = host
418
+ end
419
+
420
+ let(:result) { renderer.render }
421
+
422
+ it 'uses the value even in preview' do
423
+ assert_equal 'echo banner > /etc/issue', result
424
+ end
425
+ end
426
+ end
427
+ end
428
+ end
429
+
430
+ context 'renderer for template with variable input used' do
431
+ let(:template) { FactoryBot.build(:job_template, :template => 'echo <%= input("client_key") -%> > /etc/chef/client.pem') }
432
+ let(:renderer) { InputTemplateRenderer.new(template) }
433
+
434
+ context 'with matching input defined' do
435
+ before { renderer.template.template_inputs<< FactoryBot.build(:template_input, :name => 'client_key', :input_type => 'variable', :variable_name => 'client_key') }
436
+ let(:result) { renderer.render }
437
+
438
+ describe 'rendering' do
439
+ it 'can\'t render the content without host since we don\'t have host so no classification' do
440
+ assert_not result
441
+ end
442
+
443
+ it 'registers an error' do
444
+ result # let is lazy
445
+ refute_nil renderer.error_message
446
+ refute_empty renderer.error_message
447
+ end
448
+
449
+ context 'with host specified' do
450
+ before { User.current = FactoryBot.build(:user, :admin) }
451
+ after { User.current = nil }
452
+
453
+ before { renderer.host = FactoryBot.create(:host) }
454
+
455
+ describe 'rendering' do
456
+ it 'can\'t render the content without host since we don\'t have variable value in classification' do
457
+ assert_not result
458
+ end
459
+
460
+ it 'registers an error' do
461
+ result # let is lazy
462
+ refute_nil renderer.error_message
463
+ refute_empty renderer.error_message
464
+ end
465
+ end
466
+
467
+ describe 'preview' do
468
+ it 'should render preview' do
469
+ assert_equal 'echo $VARIABLE_INPUT[client_key] > /etc/chef/client.pem', renderer.preview
470
+ end
471
+ end
472
+
473
+
474
+ context 'with existing variable implemented as host parameter' do
475
+ let(:parameter) { FactoryBot.create(:host_parameter, :host => renderer.host, :name => 'client_key', :value => 'RSA KEY') }
476
+
477
+ describe 'rendering' do
478
+ it 'renders the value from host parameter' do
479
+ parameter
480
+ renderer.host.reload
481
+ assert_equal 'echo RSA KEY > /etc/chef/client.pem', result
482
+ end
483
+ end
484
+
485
+ describe 'preview' do
486
+ it 'should render preview' do
487
+ parameter
488
+ renderer.host.reload
489
+ assert_equal 'echo RSA KEY > /etc/chef/client.pem', renderer.preview
490
+ end
491
+ end
492
+ end
493
+ end
494
+
495
+ describe 'preview' do
496
+ it 'should render preview' do
497
+ assert_equal 'echo $VARIABLE_INPUT[client_key] > /etc/chef/client.pem', renderer.preview
498
+ end
499
+ end
500
+ end
501
+ end
502
+ end
503
+ end