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.
- checksums.yaml +15 -0
- data/LICENSE +619 -0
- data/README.md +54 -0
- data/Rakefile +47 -0
- data/app/assets/javascripts/template_input.js +9 -0
- data/app/assets/javascripts/template_invocation.js +32 -0
- data/app/assets/stylesheets/job_invocations.css.scss +35 -0
- data/app/assets/stylesheets/template_invocation.css.scss +16 -0
- data/app/controllers/api/v2/job_templates_controller.rb +108 -0
- data/app/controllers/job_invocations_controller.rb +35 -0
- data/app/controllers/job_templates_controller.rb +35 -0
- data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +40 -0
- data/app/helpers/remote_execution_helper.rb +88 -0
- data/app/lib/actions/remote_execution/run_host_job.rb +93 -0
- data/app/lib/actions/remote_execution/run_hosts_job.rb +35 -0
- data/app/lib/actions/remote_execution/run_proxy_command.rb +34 -0
- data/app/models/concerns/foreman_remote_execution/bookmark_extensions.rb +9 -0
- data/app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb +9 -0
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +19 -0
- data/app/models/concerns/foreman_remote_execution/template_extensions.rb +20 -0
- data/app/models/concerns/foreman_remote_execution/template_relations.rb +10 -0
- data/app/models/concerns/foreman_remote_execution/user_extensions.rb +9 -0
- data/app/models/input_template_renderer.rb +42 -0
- data/app/models/job_invocation.rb +21 -0
- data/app/models/job_invocation_composer.rb +210 -0
- data/app/models/job_template.rb +52 -0
- data/app/models/remote_execution_provider.rb +17 -0
- data/app/models/setting/remote_execution.rb +19 -0
- data/app/models/ssh_execution_provider.rb +2 -0
- data/app/models/targeting.rb +56 -0
- data/app/models/targeting_host.rb +9 -0
- data/app/models/template_input.rb +154 -0
- data/app/models/template_invocation.rb +13 -0
- data/app/models/template_invocation_input_value.rb +8 -0
- data/app/views/api/v2/job_templates/base.json.rabl +3 -0
- data/app/views/api/v2/job_templates/create.json.rabl +3 -0
- data/app/views/api/v2/job_templates/index.json.rabl +3 -0
- data/app/views/api/v2/job_templates/main.json.rabl +5 -0
- data/app/views/api/v2/job_templates/show.json.rabl +9 -0
- data/app/views/job_invocations/_form.html.erb +67 -0
- data/app/views/job_invocations/_tab_hosts.html.erb +33 -0
- data/app/views/job_invocations/_tab_overview.html.erb +41 -0
- data/app/views/job_invocations/index.html.erb +30 -0
- data/app/views/job_invocations/new.html.erb +8 -0
- data/app/views/job_invocations/refresh.js.erb +1 -0
- data/app/views/job_invocations/show.html.erb +21 -0
- data/app/views/job_templates/_custom_tab_headers.html.erb +2 -0
- data/app/views/job_templates/_custom_tabs.html.erb +28 -0
- data/app/views/job_templates/auto_complete_job_name.json.erb +3 -0
- data/app/views/job_templates/edit.html.erb +6 -0
- data/app/views/job_templates/index.html.erb +33 -0
- data/app/views/job_templates/new.html.erb +6 -0
- data/app/views/template_inputs/_form.html.erb +22 -0
- data/config/routes.rb +35 -0
- data/db/migrate/20150612121541_add_job_template_to_template.rb +6 -0
- data/db/migrate/20150616080015_create_template_input.rb +19 -0
- data/db/migrate/20150708133241_add_targeting.rb +25 -0
- data/db/migrate/20150708133242_add_invocation.rb +11 -0
- data/db/migrate/20150708133305_add_template_invocation.rb +22 -0
- data/db/migrate/20150812110800_add_resolved_at_to_targeting.rb +5 -0
- data/db/migrate/20150812145900_add_last_task_id_to_job_invocation.rb +6 -0
- data/db/seeds.d/60-ssh_proxy_feature.rb +2 -0
- data/lib/foreman_remote_execution/engine.rb +119 -0
- data/lib/foreman_remote_execution/version.rb +3 -0
- data/lib/foreman_remote_execution.rb +6 -0
- data/lib/tasks/foreman_remote_execution_tasks.rake +49 -0
- data/locale/Makefile +62 -0
- data/locale/en/foreman_remote_execution.po +19 -0
- data/locale/foreman_remote_execution.pot +19 -0
- data/locale/gemspec.rb +2 -0
- data/test/factories/foreman_remote_execution_factories.rb +48 -0
- data/test/functional/api/v2/job_templates_controller_test.rb +74 -0
- data/test/test_plugin_helper.rb +8 -0
- data/test/unit/actions/run_hosts_job_test.rb +40 -0
- data/test/unit/actions/run_proxy_command_test.rb +30 -0
- data/test/unit/input_template_renderer_test.rb +366 -0
- data/test/unit/job_invocation_composer_test.rb +415 -0
- data/test/unit/job_invocation_test.rb +31 -0
- data/test/unit/job_template_test.rb +5 -0
- data/test/unit/remote_execution_provider_test.rb +51 -0
- data/test/unit/targeting_test.rb +107 -0
- data/test/unit/template_input_test.rb +25 -0
- 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,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
|