foreman_remote_execution 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +619 -0
  3. data/README.md +54 -0
  4. data/Rakefile +47 -0
  5. data/app/assets/javascripts/template_input.js +9 -0
  6. data/app/assets/javascripts/template_invocation.js +32 -0
  7. data/app/assets/stylesheets/job_invocations.css.scss +35 -0
  8. data/app/assets/stylesheets/template_invocation.css.scss +16 -0
  9. data/app/controllers/api/v2/job_templates_controller.rb +108 -0
  10. data/app/controllers/job_invocations_controller.rb +35 -0
  11. data/app/controllers/job_templates_controller.rb +35 -0
  12. data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +40 -0
  13. data/app/helpers/remote_execution_helper.rb +88 -0
  14. data/app/lib/actions/remote_execution/run_host_job.rb +93 -0
  15. data/app/lib/actions/remote_execution/run_hosts_job.rb +35 -0
  16. data/app/lib/actions/remote_execution/run_proxy_command.rb +34 -0
  17. data/app/models/concerns/foreman_remote_execution/bookmark_extensions.rb +9 -0
  18. data/app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb +9 -0
  19. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +19 -0
  20. data/app/models/concerns/foreman_remote_execution/template_extensions.rb +20 -0
  21. data/app/models/concerns/foreman_remote_execution/template_relations.rb +10 -0
  22. data/app/models/concerns/foreman_remote_execution/user_extensions.rb +9 -0
  23. data/app/models/input_template_renderer.rb +42 -0
  24. data/app/models/job_invocation.rb +21 -0
  25. data/app/models/job_invocation_composer.rb +210 -0
  26. data/app/models/job_template.rb +52 -0
  27. data/app/models/remote_execution_provider.rb +17 -0
  28. data/app/models/setting/remote_execution.rb +19 -0
  29. data/app/models/ssh_execution_provider.rb +2 -0
  30. data/app/models/targeting.rb +56 -0
  31. data/app/models/targeting_host.rb +9 -0
  32. data/app/models/template_input.rb +154 -0
  33. data/app/models/template_invocation.rb +13 -0
  34. data/app/models/template_invocation_input_value.rb +8 -0
  35. data/app/views/api/v2/job_templates/base.json.rabl +3 -0
  36. data/app/views/api/v2/job_templates/create.json.rabl +3 -0
  37. data/app/views/api/v2/job_templates/index.json.rabl +3 -0
  38. data/app/views/api/v2/job_templates/main.json.rabl +5 -0
  39. data/app/views/api/v2/job_templates/show.json.rabl +9 -0
  40. data/app/views/job_invocations/_form.html.erb +67 -0
  41. data/app/views/job_invocations/_tab_hosts.html.erb +33 -0
  42. data/app/views/job_invocations/_tab_overview.html.erb +41 -0
  43. data/app/views/job_invocations/index.html.erb +30 -0
  44. data/app/views/job_invocations/new.html.erb +8 -0
  45. data/app/views/job_invocations/refresh.js.erb +1 -0
  46. data/app/views/job_invocations/show.html.erb +21 -0
  47. data/app/views/job_templates/_custom_tab_headers.html.erb +2 -0
  48. data/app/views/job_templates/_custom_tabs.html.erb +28 -0
  49. data/app/views/job_templates/auto_complete_job_name.json.erb +3 -0
  50. data/app/views/job_templates/edit.html.erb +6 -0
  51. data/app/views/job_templates/index.html.erb +33 -0
  52. data/app/views/job_templates/new.html.erb +6 -0
  53. data/app/views/template_inputs/_form.html.erb +22 -0
  54. data/config/routes.rb +35 -0
  55. data/db/migrate/20150612121541_add_job_template_to_template.rb +6 -0
  56. data/db/migrate/20150616080015_create_template_input.rb +19 -0
  57. data/db/migrate/20150708133241_add_targeting.rb +25 -0
  58. data/db/migrate/20150708133242_add_invocation.rb +11 -0
  59. data/db/migrate/20150708133305_add_template_invocation.rb +22 -0
  60. data/db/migrate/20150812110800_add_resolved_at_to_targeting.rb +5 -0
  61. data/db/migrate/20150812145900_add_last_task_id_to_job_invocation.rb +6 -0
  62. data/db/seeds.d/60-ssh_proxy_feature.rb +2 -0
  63. data/lib/foreman_remote_execution/engine.rb +119 -0
  64. data/lib/foreman_remote_execution/version.rb +3 -0
  65. data/lib/foreman_remote_execution.rb +6 -0
  66. data/lib/tasks/foreman_remote_execution_tasks.rake +49 -0
  67. data/locale/Makefile +62 -0
  68. data/locale/en/foreman_remote_execution.po +19 -0
  69. data/locale/foreman_remote_execution.pot +19 -0
  70. data/locale/gemspec.rb +2 -0
  71. data/test/factories/foreman_remote_execution_factories.rb +48 -0
  72. data/test/functional/api/v2/job_templates_controller_test.rb +74 -0
  73. data/test/test_plugin_helper.rb +8 -0
  74. data/test/unit/actions/run_hosts_job_test.rb +40 -0
  75. data/test/unit/actions/run_proxy_command_test.rb +30 -0
  76. data/test/unit/input_template_renderer_test.rb +366 -0
  77. data/test/unit/job_invocation_composer_test.rb +415 -0
  78. data/test/unit/job_invocation_test.rb +31 -0
  79. data/test/unit/job_template_test.rb +5 -0
  80. data/test/unit/remote_execution_provider_test.rb +51 -0
  81. data/test/unit/targeting_test.rb +107 -0
  82. data/test/unit/template_input_test.rb +25 -0
  83. metadata +195 -0
@@ -0,0 +1,415 @@
1
+ require 'test_plugin_helper'
2
+ RemoteExecutionProvider.register(:Ansible, OpenStruct)
3
+ RemoteExecutionProvider.register(:Mcollective, OpenStruct)
4
+
5
+ describe JobInvocationComposer do
6
+ before do
7
+ permission1 = FactoryGirl.create(:permission, :name => 'view_job_templates', :resource_type => 'JobTemplate')
8
+ permission2 = Permission.find_by_name('view_bookmarks')
9
+ permission3 = Permission.find_by_name('view_hosts')
10
+ filter1 = FactoryGirl.build(:filter, :permissions => [permission1], :search => 'name ~ testing*')
11
+ filter2 = FactoryGirl.build(:filter, :permissions => [permission2])
12
+ filter3 = FactoryGirl.build(:filter, :permissions => [permission3])
13
+ filter1.save
14
+ filter2.save
15
+ filter3.save
16
+ role = FactoryGirl.build(:role)
17
+ role.filters = filter1, filter2, filter3
18
+ role.save
19
+ User.current = FactoryGirl.build(:user)
20
+ User.current.roles<< role
21
+ User.current.save
22
+ end
23
+
24
+ let(:testing_job_template_1) { FactoryGirl.create(:job_template, :job_name => 'testing_job_template_1', :name => 'testing1', :provider_type => 'Ssh') }
25
+ let(:testing_job_template_2) { FactoryGirl.create(:job_template, :job_name => 'testing_job_template_2', :name => 'testing2', :provider_type => 'Mcollective') }
26
+ let(:testing_job_template_3) { FactoryGirl.create(:job_template, :job_name => 'testing_job_template_1', :name => 'testing3', :provider_type => 'Ssh') }
27
+ let(:unauthorized_job_template_1) { FactoryGirl.create(:job_template, :job_name => 'testing_job_template_1', :name => 'unauth1', :provider_type => 'Ssh') }
28
+ let(:unauthorized_job_template_2) { FactoryGirl.create(:job_template, :job_name => 'unauthorized_job_template_2', :name => 'unauth2', :provider_type => 'Ansible') }
29
+
30
+ let(:input1) { FactoryGirl.create(:template_input, :template => testing_job_template_1, :input_type => 'user') }
31
+ let(:input2) { FactoryGirl.create(:template_input, :template => testing_job_template_3, :input_type => 'user') }
32
+ let(:unauthorized_input1) { FactoryGirl.create(:template_input, :template => unauthorized_job_template_1, :input_type => 'user') }
33
+
34
+ let(:ansible_params) { { } }
35
+ let(:ssh_params) { { } }
36
+ let(:mcollective_params) { { } }
37
+ let(:providers_params) { { :providers => { :ansible => ansible_params, :ssh => ssh_params, :mcollective => mcollective_params } } }
38
+
39
+ context 'with general new invocation and empty params' do
40
+ let(:params) { {} }
41
+ let(:job_invocation) { JobInvocation.new }
42
+ let(:composer) { JobInvocationComposer.new(job_invocation, params) }
43
+
44
+ describe '#available_templates' do
45
+ it 'obeys authorization' do
46
+ composer # lazy load composer before stubbing
47
+ JobTemplate.expects(:authorized).with(:view_job_templates).returns(JobTemplate.scoped)
48
+ composer.available_templates
49
+ end
50
+ end
51
+
52
+ context 'job templates exist' do
53
+ before do
54
+ testing_job_template_1
55
+ testing_job_template_2
56
+ testing_job_template_3
57
+ unauthorized_job_template_1
58
+ unauthorized_job_template_2
59
+ end
60
+
61
+ describe '#available_templates_for(job_name)' do
62
+ it 'find the templates only for a given job name' do
63
+ results = composer.available_templates_for(testing_job_template_1.job_name)
64
+ results.must_include testing_job_template_1
65
+ results.wont_include testing_job_template_2
66
+ end
67
+
68
+ it 'it respects view permissions' do
69
+ results = composer.available_templates_for(testing_job_template_1.job_name)
70
+ results.wont_include unauthorized_job_template_1
71
+ end
72
+ end
73
+
74
+ describe '#available_job_names' do
75
+ let(:job_names) { composer.available_job_names }
76
+
77
+ it 'find only job names that user is granted to view' do
78
+ job_names.must_include testing_job_template_1.job_name
79
+ job_names.must_include testing_job_template_2.job_name
80
+ job_names.wont_include unauthorized_job_template_2.job_name
81
+ end
82
+
83
+ it 'every job name is listed just once' do
84
+ job_names.uniq.must_equal job_names
85
+ end
86
+ end
87
+
88
+ describe '#available_provider_types' do
89
+ let(:provider_types) { composer.available_provider_types }
90
+
91
+ it 'finds only providers which user is granted to view' do
92
+ composer.job_invocation.job_name = 'testing_job_template_1'
93
+ provider_types.must_include 'Ssh'
94
+ provider_types.wont_include 'Mcollective'
95
+ provider_types.wont_include 'Ansible'
96
+ end
97
+
98
+ it 'every provider type is listed just once' do
99
+ provider_types.uniq.must_equal provider_types
100
+ end
101
+ end
102
+
103
+ describe '#available_template_inputs' do
104
+ before do
105
+ input1
106
+ input2
107
+ unauthorized_input1
108
+ end
109
+
110
+ it 'returns only authorized inputs based on templates' do
111
+ composer.available_template_inputs.must_be_empty
112
+ end
113
+
114
+ context 'params contains job template ids' do
115
+ let(:ssh_params) { { :job_template_id => testing_job_template_1.id.to_s } }
116
+ let(:ansible_params) { { :job_template_id => '' } }
117
+ let(:mcollective_params) { { :job_template_id => '' } }
118
+ let(:params) { { :job_invocation => providers_params }.with_indifferent_access }
119
+
120
+ it 'finds the inputs only specified job templates' do
121
+ composer.available_template_inputs.must_include(input1)
122
+ composer.available_template_inputs.wont_include(input2)
123
+ composer.available_template_inputs.wont_include(unauthorized_input1)
124
+ end
125
+ end
126
+ end
127
+
128
+ describe '#needs_provider_type_selection?' do
129
+ it 'returns true if there are more than one providers respecting authorization' do
130
+ composer.stubs(:available_provider_types => [ 'Ssh', 'Ansible' ])
131
+ assert composer.needs_provider_type_selection?
132
+ end
133
+
134
+ it 'returns false if there is one provider' do
135
+ composer.stubs(:available_provider_types => [ 'Ssh' ])
136
+ refute composer.needs_provider_type_selection?
137
+ end
138
+ end
139
+
140
+ describe '#only_one_template_available?' do
141
+ context 'composer needs provider type selection' do
142
+ before { composer.stubs(:needs_provider_type_selection? => true) }
143
+
144
+ it 'returns false because it means we have at least two providers so we need to be able to disable it per provider' do
145
+ refute composer.only_one_template_available?
146
+ end
147
+ end
148
+
149
+ context 'composer does not need provider type selection' do
150
+ before { composer.stubs(:needs_provider_type_selection? => false) }
151
+
152
+ it 'returns true if there is only one template for the provider' do
153
+ composer.stubs(:templates_for_provider => [ testing_job_template_1 ])
154
+ assert composer.only_one_template_available?
155
+ end
156
+
157
+ it 'returns false if there is more than one template for the provider' do
158
+ composer.stubs(:templates_for_provider => [ testing_job_template_1, testing_job_template_3 ])
159
+ refute composer.only_one_template_available?
160
+ end
161
+ end
162
+ end
163
+
164
+ describe '#displayed_provider_types' do
165
+ # nothing to test yet
166
+ end
167
+
168
+ describe '#templates_for_provider(provider_type)' do
169
+ it 'returns all templates for a given provider respecting template permissions' do
170
+ testing_job_template_4 = FactoryGirl.create(:job_template, :job_name => 'testing_job_template_1', :name => 'testing4', :provider_type => 'Ansible')
171
+ result = composer.templates_for_provider('Ssh')
172
+ result.must_include testing_job_template_1
173
+ result.must_include testing_job_template_3
174
+ result.wont_include unauthorized_job_template_1
175
+ result.wont_include testing_job_template_4
176
+
177
+ result = composer.templates_for_provider('Ansible')
178
+ result.wont_include testing_job_template_1
179
+ result.wont_include testing_job_template_3
180
+ result.wont_include unauthorized_job_template_2
181
+ result.must_include testing_job_template_4
182
+ end
183
+ end
184
+
185
+ describe '#selected_job_templates' do
186
+ it 'returns no template if none was selected through params' do
187
+ composer.selected_job_templates.must_be_empty
188
+ end
189
+
190
+ context 'extra unavailable templates id were selected' do
191
+ let(:unauthorized) { FactoryGirl.create(:job_template, :job_name => 'testing_job_template_1', :name => 'unauth3', :provider_type => 'Ansible') }
192
+ let(:mcollective_authorized) { FactoryGirl.create(:job_template, :job_name => 'testing_job_template_1', :name => 'testing4', :provider_type => 'Mcollective') }
193
+ let(:ssh_params) { { :job_template_id => testing_job_template_1.id.to_s } }
194
+ let(:ansible_params) { { :job_template_id => unauthorized.id.to_s } }
195
+ let(:mcollective_params) { { :job_template_id => mcollective_authorized.id.to_s } }
196
+ let(:params) { { :job_invocation => providers_params }.with_indifferent_access }
197
+
198
+ it 'ignores unauthorized template' do
199
+ unauthorized # make sure unautorized exists
200
+ composer.selected_job_templates.wont_include unauthorized
201
+ end
202
+
203
+ it 'contains only authorized template specified in params' do
204
+ mcollective_authorized # make sure mcollective_authorized exists
205
+ composer.selected_job_templates.must_include testing_job_template_1
206
+ composer.selected_job_templates.must_include mcollective_authorized
207
+ composer.selected_job_templates.wont_include testing_job_template_3
208
+ end
209
+ end
210
+ end
211
+
212
+ describe '#preselect_disabled_for_provider(provider_type)' do
213
+ context 'none template was selected through params' do
214
+ it 'returns true since nothing was selected and disabled is default' do
215
+ assert composer.preselect_disabled_for_provider('Ssh')
216
+ end
217
+ end
218
+
219
+ context 'available template was selected for a specified provider through params' do
220
+ let(:ssh_params) { { :job_template_id => testing_job_template_1.id.to_s } }
221
+ let(:params) { { :job_invocation => providers_params }.with_indifferent_access }
222
+
223
+ it 'returns false because available template was selected' do
224
+ refute composer.preselect_disabled_for_provider('Ssh')
225
+ end
226
+ end
227
+ end
228
+
229
+ describe '#template_invocations' do
230
+ let(:ssh_params) do
231
+ { :job_template_id => testing_job_template_1.id.to_s,
232
+ :job_templates => {
233
+ testing_job_template_1.id.to_s => {
234
+ :input_values => { input1.id.to_s => { :value => 'value1' }, unauthorized_input1.id.to_s => { :value => 'dropped' } }
235
+ }
236
+ }
237
+ }
238
+ end
239
+ let(:params) { { :job_invocation => { :providers => { :ssh => ssh_params } } }.with_indifferent_access }
240
+ let(:invocations) { composer.template_invocations }
241
+
242
+ it 'builds template invocations based on passed params and it filters out wrong inputs' do
243
+ invocations.size.must_equal 1
244
+ invocations.first.input_values.size.must_equal 1
245
+ invocations.first.input_values.first.value.must_equal 'value1'
246
+ end
247
+ end
248
+
249
+ describe '#displayed_search_query' do
250
+ it 'is empty by default' do
251
+ composer.displayed_search_query.must_be_empty
252
+ end
253
+
254
+ let(:host) { FactoryGirl.create(:host) }
255
+ let(:bookmark) { Bookmark.create!(:query => 'b', :name => 'bookmark', :public => true, :controller => 'hosts') }
256
+
257
+ context 'all targetings parameters are present' do
258
+ let(:params) { { :targeting => { :search_query => 'a', :bookmark_id => bookmark.id }, :host_ids => [ host.id ] }.with_indifferent_access }
259
+
260
+ it 'explicit search query has highest priority' do
261
+ composer.displayed_search_query.must_equal 'a'
262
+ end
263
+ end
264
+
265
+ context 'host ids and bookmark are present' do
266
+ let(:params) { { :targeting => { :bookmark_id => bookmark.id }, :host_ids => [ host.id ] }.with_indifferent_access }
267
+
268
+ it 'hosts will be used instead of a bookmark' do
269
+ composer.displayed_search_query.must_include host.name
270
+ end
271
+ end
272
+
273
+ context 'bookmark is present' do
274
+ let(:params) { { :targeting => { :bookmark_id => bookmark.id } }.with_indifferent_access }
275
+
276
+ it 'bookmark query is used if it is available for the user' do
277
+ bookmark.update_attribute :public, false
278
+ composer.displayed_search_query.must_equal bookmark.query
279
+ end
280
+
281
+ it 'bookmark query is used if the bookmark is public' do
282
+ bookmark.owner = nil
283
+ bookmark.save(:validate => false) # skip validations so owner remains nil
284
+ composer.displayed_search_query.must_equal bookmark.query
285
+ end
286
+
287
+ it 'empty search is returned if bookmark is not owned by the user and is not public' do
288
+ bookmark.public = false
289
+ bookmark.owner = nil
290
+ bookmark.save(:validate => false) # skip validations so owner remains nil
291
+ composer.displayed_search_query.must_be_empty
292
+ end
293
+ end
294
+ end
295
+
296
+ describe '#available_bookmarks' do
297
+ it 'obeys authorization' do
298
+ composer
299
+ Bookmark.expects(:authorized).with(:view_bookmarks).returns(Bookmark.scoped)
300
+ composer.available_bookmarks
301
+ end
302
+ end
303
+
304
+ describe '#targeted_hosts_count' do
305
+ it 'obeys authorization' do
306
+ composer
307
+ Host.expects(:authorized).with(:view_hosts, Host).returns(Host.scoped)
308
+ composer.targeted_hosts_count
309
+ end
310
+
311
+ let(:host) { FactoryGirl.create(:host) }
312
+
313
+ it 'searches hosts based on displayed_search_query' do
314
+ composer.stubs(:displayed_search_query => "name = #{host.name}")
315
+ composer.targeted_hosts_count.must_equal 1
316
+ end
317
+
318
+ it 'returns 0 for queries with syntax errors' do
319
+ composer.stubs(:displayed_search_query => "name = ")
320
+ composer.targeted_hosts_count.must_equal 0
321
+ end
322
+ end
323
+
324
+ describe '#template_invocation_input_value_for(input)' do
325
+ let(:value1) { composer.template_invocation_input_value_for(input1) }
326
+ it 'returns new empty input value if there is no invocation' do
327
+ assert value1.new_record?
328
+ value1.value.must_be_empty
329
+ end
330
+
331
+ context 'there are invocations without input values for a given input' do
332
+ let(:ssh_params) do
333
+ { :job_template_id => testing_job_template_1.id.to_s,
334
+ :job_templates => {
335
+ testing_job_template_1.id.to_s => {
336
+ :input_values => { }
337
+ } } }
338
+ end
339
+ let(:params) { { :job_invocation => { :providers => { :ssh => ssh_params } } }.with_indifferent_access }
340
+
341
+ it 'returns new empty input value' do
342
+ assert value1.new_record?
343
+ value1.value.must_be_empty
344
+ end
345
+ end
346
+
347
+ context 'there are invocations with input values for a given input' do
348
+ let(:ssh_params) do
349
+ { :job_template_id => testing_job_template_1.id.to_s,
350
+ :job_templates => {
351
+ testing_job_template_1.id.to_s => {
352
+ :input_values => { input1.id.to_s => { :value => 'value1' } }
353
+ } } }
354
+ end
355
+ let(:params) { { :job_invocation => { :providers => { :ssh => ssh_params } } }.with_indifferent_access }
356
+
357
+ it 'finds the value among template invocations' do
358
+ value1.value.must_equal 'value1'
359
+ end
360
+ end
361
+ end
362
+
363
+ describe '#valid?' do
364
+ let(:host) { FactoryGirl.create(:host) }
365
+ let(:ssh_params) do
366
+ { :job_template_id => testing_job_template_1.id.to_s,
367
+ :job_templates => {
368
+ testing_job_template_1.id.to_s => {
369
+ :input_values => { input1.id.to_s => { :value => 'value1' } }
370
+ } } }
371
+ end
372
+ let(:params) { { :job_invocation => { :providers => { :ssh => ssh_params } }, :targeting => { :search_query => "name = #{host.name}" } }.with_indifferent_access }
373
+
374
+ it 'validates all associated objects even if some of the is invalid' do
375
+ composer
376
+ job_invocation.expects(:valid?).returns(false)
377
+ composer.targeting.expects(:valid?).returns(false)
378
+ composer.template_invocations.each { |invocation| invocation.expects(:valid?).returns(false) }
379
+ refute composer.valid?
380
+ end
381
+ end
382
+
383
+ describe '#save' do
384
+ it 'triggers save on job_invocation if it is valid' do
385
+ composer.stubs(:valid? => true)
386
+ job_invocation.expects(:save)
387
+ composer.save
388
+ end
389
+
390
+ it 'does not trigger save on job_invocation if it is invalid' do
391
+ composer.stubs(:valid? => false)
392
+ job_invocation.expects(:save).never
393
+ composer.save
394
+ end
395
+ end
396
+
397
+ describe '#job_name' do
398
+ it 'triggers job_name on job_invocation' do
399
+ composer
400
+ job_invocation.expects(:job_name)
401
+ composer.job_name
402
+ end
403
+ end
404
+
405
+ describe '#targeting' do
406
+ it 'triggers targeting on job_invocation' do
407
+ composer
408
+ job_invocation.expects(:targeting)
409
+ composer.targeting
410
+ end
411
+ end
412
+
413
+ end
414
+ end
415
+ end
@@ -0,0 +1,31 @@
1
+ require 'test_plugin_helper'
2
+
3
+ describe JobInvocation do
4
+
5
+ let(:job_invocation) { FactoryGirl.build(:job_invocation) }
6
+ let(:template) { FactoryGirl.create(:job_template, :with_input) }
7
+
8
+ context 'Able to be created' do
9
+ it { assert job_invocation.save }
10
+ end
11
+
12
+ context 'Requires targeting' do
13
+ before { job_invocation.targeting = nil }
14
+
15
+ it { refute_valid job_invocation }
16
+ end
17
+
18
+ context 'has template invocations with input values' do
19
+ let(:job_invocation) { FactoryGirl.create(:job_invocation, :with_template) }
20
+
21
+ before do
22
+ @input_value = FactoryGirl.create(:template_invocation_input_value,
23
+ :template_invocation => job_invocation.template_invocations.first,
24
+ :template_input => template.template_inputs.first)
25
+
26
+ end
27
+
28
+ it { refute job_invocation.reload.template_invocations.empty? }
29
+ it { refute job_invocation.template_invocations.first.input_values.empty? }
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ require 'test_plugin_helper'
2
+
3
+ describe JobTemplate do
4
+
5
+ end
@@ -0,0 +1,51 @@
1
+ require 'test_plugin_helper'
2
+
3
+ describe RemoteExecutionProvider do
4
+ describe '.providers' do
5
+ let(:providers) { RemoteExecutionProvider.providers }
6
+ it { providers.must_be_kind_of HashWithIndifferentAccess }
7
+ it 'makes providers accessible using symbol' do
8
+ providers[:Ssh].must_equal SSHExecutionProvider
9
+ end
10
+ it 'makes providers accessible using string' do
11
+ providers['Ssh'].must_equal SSHExecutionProvider
12
+ end
13
+ end
14
+
15
+ describe '.register_provider' do
16
+ let(:new_provider) { RemoteExecutionProvider.providers[:new] }
17
+ it { new_provider.must_be_nil }
18
+
19
+ context 'registers a provider under key :new' do
20
+ before { RemoteExecutionProvider.register(:new, String) }
21
+ it { new_provider.must_equal String }
22
+ end
23
+ end
24
+
25
+ describe '.provider_for' do
26
+ it 'accepts symbols' do
27
+ RemoteExecutionProvider.provider_for(:Ssh).must_equal SSHExecutionProvider
28
+ end
29
+
30
+ it 'accepts strings' do
31
+ RemoteExecutionProvider.provider_for('Ssh').must_equal SSHExecutionProvider
32
+ end
33
+ end
34
+
35
+ describe '.provider_names' do
36
+ let(:provider_names) { RemoteExecutionProvider.provider_names }
37
+
38
+ it 'returns only strings' do
39
+ provider_names.each do |name|
40
+ name.must_be_kind_of String
41
+ end
42
+ end
43
+
44
+ context 'provider is registetered under :custom symbol' do
45
+ before { RemoteExecutionProvider.register(:Custom, String) }
46
+
47
+ it { provider_names.must_include 'Ssh' }
48
+ it { provider_names.must_include 'Custom' }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,107 @@
1
+ require 'test_plugin_helper'
2
+
3
+ describe Targeting do
4
+ let(:targeting) { FactoryGirl.build(:targeting) }
5
+ let(:bookmark) { bookmarks(:one) }
6
+ let(:host) { FactoryGirl.create(:host) }
7
+
8
+ before do
9
+ bookmark.query = "name = bar"
10
+ end
11
+
12
+ context 'able to be created with search term' do
13
+ before { targeting.search_query = "name = foo" }
14
+ it { assert targeting.save }
15
+ end
16
+
17
+ context 'able to be created with a bookmark' do
18
+ before do
19
+ targeting.search_query = nil
20
+ targeting.bookmark = bookmark
21
+ end
22
+ it { assert_valid targeting }
23
+ end
24
+
25
+ context 'cannot create without search term or bookmark' do
26
+ before do
27
+ targeting.targeting_type = Targeting::DYNAMIC_TYPE
28
+ targeting.search_query = nil
29
+ targeting.bookmark = nil
30
+ end
31
+ it { refute_valid targeting }
32
+ end
33
+
34
+ context 'can resolve hosts via query' do
35
+ before do
36
+ targeting.user = users(:admin)
37
+ targeting.search_query = "name = #{host.name}"
38
+ targeting.resolve_hosts!
39
+ end
40
+
41
+ it { targeting.hosts.must_include(host) }
42
+ end
43
+
44
+ context 'can delete a user' do
45
+ before do
46
+ targeting.user = users(:one)
47
+ targeting.save!
48
+ users(:one).destroy
49
+ end
50
+
51
+ it { assert targeting.reload.user.nil? }
52
+ it do
53
+ -> { targeting.resolve_hosts! }.must_raise(Foreman::Exception)
54
+ end
55
+ end
56
+
57
+ context 'can delete a host' do
58
+ before do
59
+ targeting.hosts << host
60
+ targeting.save!
61
+ host.destroy
62
+ end
63
+
64
+ it { targeting.reload.hosts.must_be_empty }
65
+ end
66
+
67
+ describe '#build_query_from_hosts(ids)' do
68
+ let(:second_host) { FactoryGirl.create(:host) }
69
+
70
+ before do
71
+ host
72
+ second_host
73
+ end
74
+
75
+ context 'for two hosts' do
76
+ let(:query) { targeting.build_query_from_hosts([ host.id, second_host.id ]) }
77
+
78
+ it 'builds query using host names joining with or' do
79
+ query.must_include "name = #{host.name}"
80
+ query.must_include "name = #{second_host.name}"
81
+ query.must_include ' or '
82
+
83
+ Host.search_for(query).must_include host
84
+ Host.search_for(query).must_include second_host
85
+ end
86
+ end
87
+
88
+ context 'for one host' do
89
+ let(:query) { targeting.build_query_from_hosts([ host.id ]) }
90
+
91
+ it 'builds query using host name' do
92
+ query.must_equal "name = #{host.name}"
93
+ Host.search_for(query).must_include host
94
+ Host.search_for(query).wont_include second_host
95
+ end
96
+ end
97
+
98
+ context 'for no id' do
99
+ let(:query) { targeting.build_query_from_hosts([]) }
100
+
101
+ it 'builds query to find all hosts' do
102
+ Host.search_for(query).must_include host
103
+ Host.search_for(query).must_include second_host
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,25 @@
1
+ require 'test_plugin_helper'
2
+
3
+ describe TemplateInput do
4
+ let(:template_input) { FactoryGirl.build(:template_input) }
5
+
6
+ context 'user input' do
7
+ before { template_input.input_type = 'user' }
8
+ it { assert template_input.user_template_input? }
9
+ end
10
+
11
+ context 'fact input' do
12
+ before { template_input.input_type = 'fact' }
13
+ it { assert template_input.fact_template_input? }
14
+ end
15
+
16
+ context 'variable input' do
17
+ before { template_input.input_type = 'variable' }
18
+ it { assert template_input.variable_template_input? }
19
+ end
20
+
21
+ context 'puppet parameter input' do
22
+ before { template_input.input_type = 'puppet_parameter' }
23
+ it { assert template_input.puppet_parameter_template_input? }
24
+ end
25
+ end