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,86 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class RemoteExecutionFeatureTest < ActiveSupport::TestCase
4
+ should validate_presence_of(:name)
5
+ should validate_presence_of(:label)
6
+ should validate_uniqueness_of(:name)
7
+ should validate_uniqueness_of(:label)
8
+
9
+ let(:install_feature) do
10
+ RemoteExecutionFeature.register(:katello_install_package, N_('Katello: Install package'),
11
+ :description => 'Install package via Katello user interface',
12
+ :provided_inputs => ['package'])
13
+ end
14
+
15
+ let(:package_template) do
16
+ FactoryBot.create(:job_template).tap do |job_template|
17
+ job_template.job_category = 'Package Action'
18
+ job_template.name = 'Package Action - SSH Default'
19
+ job_template.template_inputs.create(:name => 'package', :input_type => 'user')
20
+ end
21
+ end
22
+
23
+ let(:host) { FactoryBot.create(:host) }
24
+
25
+ before do
26
+ User.current = users :admin
27
+ install_feature.update!(:job_template_id => package_template.id)
28
+ end
29
+
30
+ describe 'composer' do
31
+ it 'prepares composer for given feature based on the mapping' do
32
+ composer = JobInvocationComposer.for_feature(:katello_install_package, host, :package => 'zsh')
33
+ assert composer.valid?
34
+ assert_equal 1, composer.pattern_template_invocations.size
35
+ template_invocation = composer.pattern_template_invocations.first
36
+ assert_equal package_template, template_invocation.template
37
+ assert_equal 1, template_invocation.input_values.size
38
+
39
+ input_value = template_invocation.input_values.first
40
+ assert_equal 'zsh', input_value.value
41
+ assert_equal 'package', input_value.template_input.name
42
+
43
+ assert_equal "name ^ (#{host.name})", composer.targeting.search_query
44
+ end
45
+
46
+ it 'updates the feature when attributes change' do
47
+ updated_feature = RemoteExecutionFeature.register(install_feature.label, N_('Katello: Install package'),
48
+ :description => 'New description',
49
+ :provided_inputs => ['package', 'force'])
50
+ updated_feature.reload
51
+ assert_equal install_feature.id, updated_feature.id
52
+ assert_equal 'New description', updated_feature.description
53
+ assert_equal ['package', 'force'], updated_feature.provided_input_names
54
+ end
55
+ end
56
+
57
+ describe '.register' do
58
+ it "creates a feature if it's missing" do
59
+ feature = RemoteExecutionFeature.register('new_feature_that_does_not_exist', 'name')
60
+ assert_predicate feature, :persisted?
61
+ assert_equal 'new_feature_that_does_not_exist', feature.label
62
+ assert_equal 'name', feature.name
63
+ assert_not feature.host_action_button
64
+ end
65
+
66
+ it 'creates a feature with host action flag' do
67
+ feature = RemoteExecutionFeature.register('new_feature_that_does_not_exist_button', 'name', :host_action_button => true)
68
+ assert_predicate feature, :persisted?
69
+ assert feature.host_action_button
70
+ end
71
+
72
+ it 'created feature with host action flag can be found using named scope' do
73
+ feature = RemoteExecutionFeature.register('new_feature_that_does_not_exist_button', 'name', :host_action_button => true)
74
+ assert_includes RemoteExecutionFeature.with_host_action_button, feature
75
+ end
76
+
77
+
78
+ it 'updates a feature if it exists' do
79
+ existing = FactoryBot.create(:remote_execution_feature, :name => 'existing_feature_withou_action_button')
80
+ feature = RemoteExecutionFeature.register(existing.label, existing.name, :host_action_button => true)
81
+ assert_predicate feature, :persisted?
82
+ existing.reload
83
+ assert existing.host_action_button
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,298 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class RemoteExecutionProviderTest < ActiveSupport::TestCase
4
+ describe '.providers' do
5
+ let(:providers) { RemoteExecutionProvider.providers }
6
+ it { assert_kind_of HashWithIndifferentAccess, providers }
7
+ it 'makes providers accessible using symbol' do
8
+ assert_equal SSHExecutionProvider, providers[:SSH]
9
+ assert_equal ScriptExecutionProvider, providers[:script]
10
+ end
11
+ it 'makes providers accessible using string' do
12
+ assert_equal SSHExecutionProvider, providers['SSH']
13
+ assert_equal ScriptExecutionProvider, providers['script']
14
+ end
15
+ end
16
+
17
+ describe '.register_provider' do
18
+ before { RemoteExecutionProvider.providers.delete(:new) }
19
+ let(:new_provider) { RemoteExecutionProvider.providers[:new] }
20
+ it { assert_nil new_provider }
21
+
22
+ context 'registers a provider under key :new' do
23
+ before { RemoteExecutionProvider.register(:new, String) }
24
+ it { assert_equal String, new_provider }
25
+ end
26
+ end
27
+
28
+ describe '.provider_for' do
29
+ it 'accepts symbols' do
30
+ assert_equal SSHExecutionProvider, RemoteExecutionProvider.provider_for(:SSH)
31
+ end
32
+
33
+ it 'accepts strings' do
34
+ assert_equal SSHExecutionProvider, RemoteExecutionProvider.provider_for('SSH')
35
+ end
36
+
37
+ it 'returns a default one if unknown value is provided' do
38
+ assert_equal ScriptExecutionProvider, RemoteExecutionProvider.provider_for('WinRM')
39
+ end
40
+ end
41
+
42
+ describe '.provider_names' do
43
+ let(:provider_names) { RemoteExecutionProvider.provider_names }
44
+
45
+ it 'returns only strings' do
46
+ provider_names.each do |name|
47
+ assert_kind_of String, name
48
+ end
49
+ end
50
+
51
+ context 'provider is registetered under :custom symbol' do
52
+ before { RemoteExecutionProvider.register(:Custom, String) }
53
+
54
+ it { assert_includes provider_names, 'SSH' }
55
+ it { assert_includes provider_names, 'Custom' }
56
+ end
57
+ end
58
+
59
+ describe '.proxy_feature' do
60
+ # rubocop:disable Naming/ConstantName
61
+ it 'handles provider subclasses properly' do
62
+ old = ::RemoteExecutionProvider
63
+
64
+ class P2 < old
65
+ end
66
+ ::RemoteExecutionProvider = P2
67
+
68
+ class CustomProvider < ::RemoteExecutionProvider
69
+ end
70
+
71
+ ::RemoteExecutionProvider.register('custom', CustomProvider)
72
+
73
+ feature = CustomProvider.proxy_feature
74
+ assert_equal 'custom', feature
75
+ ensure
76
+ ::RemoteExecutionProvider = old
77
+ end
78
+ # rubocop:enable Naming/ConstantName
79
+ end
80
+
81
+ describe '.provider_proxy_features' do
82
+ it 'returns correct values' do
83
+ RemoteExecutionProvider.stubs(:providers).returns(
84
+ :SSH => SSHExecutionProvider,
85
+ :script => ScriptExecutionProvider
86
+ )
87
+
88
+ features = RemoteExecutionProvider.provider_proxy_features
89
+ assert_includes features, 'SSH'
90
+ assert_includes features, 'Script'
91
+ RemoteExecutionProvider.unstub(:providers)
92
+ end
93
+
94
+ it 'can deal with non-arrays' do
95
+ provider = OpenStruct.new(proxy_feature: 'Testing')
96
+ RemoteExecutionProvider.stubs(:providers).returns(:testing => provider)
97
+ features = RemoteExecutionProvider.provider_proxy_features
98
+ assert_includes features, 'Testing'
99
+ RemoteExecutionProvider.unstub(:providers)
100
+ end
101
+ end
102
+
103
+ describe '.host_setting' do
104
+ let(:host) { FactoryBot.create(:host) }
105
+
106
+ it 'honors falsey values set as a host parameter' do
107
+ key = 'remote_execution_connect_by_ip'
108
+ Setting[key] = true
109
+ host.parameters << HostParameter.new(name: key, value: false)
110
+
111
+ refute RemoteExecutionProvider.host_setting(host, key)
112
+ end
113
+ end
114
+
115
+ describe SSHExecutionProvider do
116
+ before { User.current = FactoryBot.build(:user, :admin) }
117
+ after { User.current = nil }
118
+
119
+ let(:job_invocation) { FactoryBot.create(:job_invocation, :with_template) }
120
+ let(:template_invocation) { job_invocation.pattern_template_invocations.first }
121
+ let(:host) { FactoryBot.create(:host) }
122
+ let(:proxy_options) { SSHExecutionProvider.proxy_command_options(template_invocation, host) }
123
+ let(:secrets) { SSHExecutionProvider.secrets(host) }
124
+
125
+ describe 'effective user' do
126
+ it 'takes the effective user from value from the template invocation' do
127
+ template_invocation.effective_user = 'my user'
128
+ assert_equal 'my user', proxy_options[:effective_user]
129
+ end
130
+ end
131
+
132
+ describe 'ssh user' do
133
+ it 'uses the remote_execution_ssh_user on the host param' do
134
+ host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_user', :value => 'my user')
135
+ assert_equal 'my user', proxy_options[:ssh_user]
136
+ end
137
+ end
138
+
139
+ describe 'effective user password' do
140
+ it 'uses the remote_execution_effective_user_password on the host param' do
141
+ host.params['remote_execution_effective_user_password'] = 'mypassword'
142
+ host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_effective_user_password', :value => 'mypassword')
143
+ assert_nil proxy_options[:effective_user_password]
144
+ assert_equal 'mypassword', secrets[:effective_user_password]
145
+ end
146
+ end
147
+
148
+ describe 'sudo' do
149
+ it 'uses the remote_execution_effective_user_method on the host param' do
150
+ host.params['remote_execution_effective_user_method'] = 'sudo'
151
+ method_param = FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_effective_user_method', :value => 'sudo')
152
+ host.host_parameters << method_param
153
+ assert_equal 'sudo', proxy_options[:effective_user_method]
154
+ method_param.update!(:value => 'su')
155
+ host.clear_host_parameters_cache!
156
+ proxy_options = SSHExecutionProvider.proxy_command_options(template_invocation, host)
157
+ assert_equal 'su', proxy_options[:effective_user_method]
158
+ method_param.update!(:value => 'dzdo')
159
+ host.clear_host_parameters_cache!
160
+ proxy_options = SSHExecutionProvider.proxy_command_options(template_invocation, host)
161
+ assert_equal 'dzdo', proxy_options[:effective_user_method]
162
+ end
163
+ end
164
+
165
+ describe 'ssh port from settings' do
166
+ before do
167
+ Setting[:remote_execution_ssh_port] = '66'
168
+ end
169
+
170
+ it 'has ssh port changed in settings and check return type' do
171
+ assert_kind_of Integer, proxy_options[:ssh_port]
172
+ assert_equal 66, proxy_options[:ssh_port]
173
+ end
174
+ end
175
+
176
+ describe 'ssh port from params' do
177
+ it 'takes ssh port number from params and check return type' do
178
+ host.params['remote_execution_ssh_port'] = '30'
179
+ host.host_parameters << FactoryBot.build(:host_parameter, :name => 'remote_execution_ssh_port', :value => '30')
180
+ host.clear_host_parameters_cache!
181
+ assert_kind_of Integer, proxy_options[:ssh_port]
182
+ assert_equal 30, proxy_options[:ssh_port]
183
+ end
184
+ end
185
+
186
+ describe 'cleanup working directories setting' do
187
+ before do
188
+ Setting[:remote_execution_cleanup_working_dirs] = false
189
+ end
190
+
191
+ it 'updates the value via settings' do
192
+ refute proxy_options[:cleanup_working_dirs]
193
+ end
194
+ end
195
+
196
+ describe 'cleanup working directories from parameters' do
197
+ it 'takes the value from host parameters' do
198
+ host.params['remote_execution_cleanup_working_dirs'] = 'false'
199
+ host.host_parameters << FactoryBot.build(:host_parameter, :name => 'remote_execution_cleanup_working_dirs', :value => 'false')
200
+ host.clear_host_parameters_cache!
201
+ refute proxy_options[:cleanup_working_dirs]
202
+ end
203
+ end
204
+
205
+ describe '#find_ip_or_hostname' do
206
+ let(:host) do
207
+ FactoryBot.create(:host) do |host|
208
+ host.interfaces = [FactoryBot.build(:nic_managed, flags.merge(:ip => nil, :name => 'somehost.somedomain.org', :primary => true)),
209
+ FactoryBot.build(:nic_managed, flags.merge(:ip => '127.0.0.1'))]
210
+ end.reload
211
+ # Reassigning the interfaces triggers the on-deletion ssh key removal for the interface which is being replaced
212
+ # This has an undesired side effect of caching the original interface as execution one which made the tests
213
+ # give wrong results. Reloading the host wipes the cache
214
+ end
215
+
216
+ let(:flags) do
217
+ { :primary => false, :provision => false, :execution => false }
218
+ end
219
+
220
+ it 'gets fqdn from flagged interfaces if not preferring ips' do
221
+ # falling to primary interface
222
+ assert_equal 'somehost.somedomain.org', SSHExecutionProvider.find_ip_or_hostname(host)
223
+
224
+ # execution wins if present
225
+ execution_interface = FactoryBot.build(:nic_managed,
226
+ flags.merge(:execution => true, :name => 'special.somedomain.org'))
227
+ host.interfaces << execution_interface
228
+ host.primary_interface.update(:execution => false)
229
+ host.interfaces.each(&:save)
230
+ host.reload
231
+ assert_equal execution_interface.fqdn, SSHExecutionProvider.find_ip_or_hostname(host)
232
+ end
233
+
234
+ it 'gets ip from flagged interfaces' do
235
+ host.host_params['remote_execution_connect_by_ip'] = true
236
+ # no ip address set on relevant interface - fallback to fqdn
237
+ assert_equal 'somehost.somedomain.org', SSHExecutionProvider.find_ip_or_hostname(host)
238
+
239
+ # provision interface with ip while primary without
240
+ provision_interface = FactoryBot.build(:nic_managed,
241
+ flags.merge(:provision => true, :ip => '10.0.0.1'))
242
+ host.interfaces << provision_interface
243
+ host.primary_interface.update(:provision => false)
244
+ host.interfaces.each(&:save)
245
+ host.reload
246
+ assert_equal provision_interface.ip, SSHExecutionProvider.find_ip_or_hostname(host)
247
+
248
+ # both primary and provision interface have IPs: the primary wins
249
+ host.primary_interface.update(:ip => '10.0.0.2', :execution => false)
250
+ host.reload
251
+ assert_equal host.primary_interface.ip, SSHExecutionProvider.find_ip_or_hostname(host)
252
+
253
+ # there is an execution interface with IP: it wins
254
+ execution_interface = FactoryBot.build(:nic_managed,
255
+ flags.merge(:execution => true, :ip => '10.0.0.3'))
256
+ host.interfaces << execution_interface
257
+ host.primary_interface.update(:execution => false)
258
+ host.interfaces.each(&:save)
259
+ host.reload
260
+ assert_equal execution_interface.ip, SSHExecutionProvider.find_ip_or_hostname(host)
261
+
262
+ # there is an execution interface with both IPv6 and IPv4: IPv4 is being preferred over IPv6 by default
263
+ execution_interface = FactoryBot.build(:nic_managed,
264
+ flags.merge(:execution => true, :ip => '10.0.0.4', :ip6 => 'fd00::4'))
265
+ host.interfaces = [execution_interface]
266
+ host.interfaces.each(&:save)
267
+ host.reload
268
+ assert_equal execution_interface.ip, SSHExecutionProvider.find_ip_or_hostname(host)
269
+ end
270
+
271
+ it 'gets ipv6 from flagged interfaces with IPv6 preference' do
272
+ host.host_params['remote_execution_connect_by_ip_prefer_ipv6'] = true
273
+ host.host_params['remote_execution_connect_by_ip'] = true
274
+
275
+ # there is an execution interface with both IPv6 and IPv4: IPv6 is being preferred over IPv4 by host parameter configuration
276
+ execution_interface = FactoryBot.build(:nic_managed,
277
+ flags.merge(:execution => true, :ip => '10.0.0.4', :ip6 => 'fd00::4'))
278
+ host.interfaces = [execution_interface]
279
+ host.interfaces.each(&:save)
280
+ host.reload
281
+ assert_equal execution_interface.ip6, SSHExecutionProvider.find_ip_or_hostname(host)
282
+ end
283
+
284
+ it 'gets ipv6 from flagged interfaces with IPv4 preference but without IPv4 address' do
285
+ host.host_params['remote_execution_connect_by_ip_prefer_ipv6'] = false
286
+ host.host_params['remote_execution_connect_by_ip'] = true
287
+
288
+ # there is an execution interface with both IPv6 and IPv4: IPv6 is being preferred over IPv4 by host parameter configuration
289
+ execution_interface = FactoryBot.build(:nic_managed,
290
+ flags.merge(:execution => true, :ip => nil, :ip6 => 'fd00::4'))
291
+ host.interfaces = [execution_interface]
292
+ host.interfaces.each(&:save)
293
+ host.reload
294
+ assert_equal execution_interface.ip6, SSHExecutionProvider.find_ip_or_hostname(host)
295
+ end
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,49 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class RendererScopeInputTest < ActiveSupport::TestCase
4
+ let(:source) { Foreman::Renderer::Source::String.new(content: '') }
5
+
6
+ describe 'caching helper in real mode' do
7
+ let(:input) do
8
+ input = ForemanRemoteExecution::Renderer::Scope::Input.new(source: source)
9
+ input.stubs(:invocation => OpenStruct.new(:job_invocation_id => 1))
10
+ input
11
+ end
12
+
13
+ it 'caches the value under given key' do
14
+ i = 1
15
+ result = input.cached('some_key') { i }
16
+ assert_equal 1, result
17
+
18
+ i += 1
19
+ result = input.cached('some_key') { i }
20
+ assert_equal 1, result
21
+
22
+ i += 1
23
+ result = input.cached('different_key') { i }
24
+ assert_equal 3, result
25
+ end
26
+ end
27
+
28
+ describe 'caching helper in preview mode' do
29
+ let(:input) do
30
+ input = ForemanRemoteExecution::Renderer::Scope::Input.new(source: source)
31
+ input.stubs(:invocation => OpenStruct.new(:job_invocation_id => 1), :preview? => true)
32
+ input
33
+ end
34
+
35
+ it 'does not cache the value' do
36
+ i = 1
37
+ result = input.cached('some_key') { i }
38
+ assert_equal 1, result
39
+
40
+ i += 1
41
+ result = input.cached('some_key') { i }
42
+ assert_equal 2, result
43
+
44
+ i += 1
45
+ result = input.cached('different_key') { i }
46
+ assert_equal 3, result
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,206 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class TargetingTest < ActiveSupport::TestCase
4
+ let(:targeting) { FactoryBot.build(:targeting) }
5
+ let(:bookmark) { bookmarks(:one) }
6
+ let(:host) { FactoryBot.create(:host) }
7
+
8
+ before do
9
+ bookmark.query = 'name = bar'
10
+ end
11
+
12
+ describe '#resolved?' do
13
+ context 'resolved_at is nil' do
14
+ before { targeting.resolved_at = nil }
15
+ it { assert_not targeting.resolved? }
16
+ end
17
+
18
+ context 'resolved_at is set' do
19
+ before { targeting.resolved_at = Time.now.getlocal }
20
+ it { assert targeting.resolved? }
21
+ end
22
+
23
+ end
24
+
25
+ context 'able to be created with search term' do
26
+ before { targeting.search_query = 'name = foo' }
27
+ it { assert targeting.save }
28
+ end
29
+
30
+ context 'able to be created with a bookmark' do
31
+ before do
32
+ targeting.search_query = nil
33
+ targeting.bookmark = bookmark
34
+ end
35
+ it { assert_valid targeting }
36
+ end
37
+
38
+ context 'cannot create without search term or bookmark' do
39
+ before do
40
+ targeting.targeting_type = Targeting::DYNAMIC_TYPE
41
+ targeting.search_query = ''
42
+ targeting.bookmark = nil
43
+ end
44
+ it { refute_valid targeting }
45
+ end
46
+
47
+ context 'can resolve hosts via query' do
48
+ before do
49
+ targeting.user = users(:admin)
50
+ targeting.search_query = "name = #{host.name}"
51
+ targeting.resolve_hosts!
52
+ end
53
+
54
+ it { assert_includes targeting.hosts, host }
55
+ end
56
+
57
+ context 'can delete a user' do
58
+ before do
59
+ targeting.user = users(:one)
60
+ targeting.save!
61
+ users(:one).destroy
62
+ end
63
+
64
+ it { assert_nil targeting.reload.user }
65
+ it do
66
+ assert_raises(Foreman::Exception) { targeting.resolve_hosts! }
67
+ end
68
+ end
69
+
70
+ context 'can delete a host' do
71
+ before do
72
+ targeting.hosts << host
73
+ targeting.save!
74
+ host.destroy
75
+ end
76
+
77
+ it { assert_empty targeting.reload.hosts }
78
+ end
79
+
80
+ describe '#resolve_hosts!' do
81
+ let(:second_host) { FactoryBot.create(:host) }
82
+ let(:infra_host) { FactoryBot.create(:host, :with_infrastructure_facet) }
83
+ let(:targeting) { FactoryBot.build(:targeting) }
84
+
85
+ before do
86
+ host
87
+ second_host
88
+ infra_host
89
+ end
90
+
91
+ context 'with infrastructure host permission' do
92
+ before do
93
+ setup_user('view', 'hosts')
94
+ setup_user('execute_jobs_on', 'infrastructure_hosts')
95
+ end
96
+
97
+ it 'resolves all hosts' do
98
+ hosts = [host, second_host, infra_host]
99
+ targeting.search_query = "name ^ (#{hosts.map(&:name).join(',')})"
100
+ targeting.user = User.current
101
+ targeting.resolve_hosts!
102
+
103
+ assert_includes targeting.hosts, host
104
+ assert_includes targeting.hosts, second_host
105
+ assert_includes targeting.hosts, infra_host
106
+ end
107
+ end
108
+
109
+ context 'without infrastructure host permission' do
110
+ before { setup_user('view', 'hosts') }
111
+
112
+ it 'ignores infrastructure hosts' do
113
+ hosts = [host, second_host, infra_host]
114
+ targeting.search_query = "name ^ (#{hosts.map(&:name).join(',')})"
115
+ targeting.user = User.current
116
+ targeting.resolve_hosts!
117
+
118
+ assert_includes targeting.hosts, host
119
+ assert_includes targeting.hosts, second_host
120
+ refute_includes targeting.hosts, infra_host
121
+ end
122
+ end
123
+ end
124
+
125
+ describe '#build_query_from_hosts(ids)' do
126
+ let(:second_host) { FactoryBot.create(:host) }
127
+ let(:infra_host) { FactoryBot.create(:host, :with_infrastructure_facet) }
128
+
129
+ before do
130
+ host
131
+ second_host
132
+ infra_host
133
+ end
134
+
135
+ context 'for two hosts' do
136
+ let(:query) { Targeting.build_query_from_hosts([ host.id, second_host.id, infra_host.id ]) }
137
+
138
+ it 'builds query using host names joining inside ^' do
139
+ assert_includes query, host.name
140
+ assert_includes query, second_host.name
141
+ assert_includes query, infra_host.name
142
+ assert_includes query, 'name ^'
143
+
144
+ assert_includes Host.search_for(query), host
145
+ assert_includes Host.search_for(query), second_host
146
+ assert_includes Host.search_for(query), infra_host
147
+ end
148
+ end
149
+
150
+ context 'for one host' do
151
+ let(:query) { Targeting.build_query_from_hosts([ host.id ]) }
152
+
153
+ it 'builds query using host name' do
154
+ assert_equal "name ^ (#{host.name})", query
155
+ assert_includes Host.search_for(query), host
156
+ refute_includes Host.search_for(query), second_host
157
+ refute_includes Host.search_for(query), infra_host
158
+ end
159
+ end
160
+
161
+ context 'for no id' do
162
+ let(:query) { Targeting.build_query_from_hosts([]) }
163
+
164
+ it 'builds query to find all hosts' do
165
+ assert_includes Host.search_for(query), host
166
+ assert_includes Host.search_for(query), second_host
167
+ assert_includes Host.search_for(query), infra_host
168
+ end
169
+ end
170
+
171
+ context 'without infrastructure host permission' do
172
+ before { User.current = nil }
173
+
174
+ it 'ignores the infrastructure host' do
175
+ query = Targeting.build_query_from_hosts([host.id, second_host.id, infra_host.id])
176
+ assert_includes query, host.name
177
+ assert_includes query, second_host.name
178
+ refute_includes query, infra_host.name
179
+ assert_includes query, 'name ^'
180
+
181
+ assert_includes Host.search_for(query), host
182
+ assert_includes Host.search_for(query), second_host
183
+ refute_includes Host.search_for(query), infra_host
184
+ end
185
+ end
186
+ end
187
+
188
+ context 'randomized ordering' do
189
+ let(:targeting) { FactoryBot.build(:targeting, :with_randomized_ordering) }
190
+ let(:hosts) { (0..4).map { FactoryBot.create(:host) } }
191
+
192
+ it 'loads the hosts in random order' do
193
+ rng = Random.new(4) # Chosen by a fair dice roll
194
+ Random.stubs(:new).returns(rng)
195
+ hosts
196
+ targeting.search_query = 'name ~ host*'
197
+ targeting.user = users(:admin)
198
+ targeting.resolve_hosts!
199
+ randomized_host_ids = targeting.hosts.map(&:id)
200
+ host_ids = hosts.map(&:id)
201
+
202
+ assert_not_equal host_ids, randomized_host_ids
203
+ assert_equal host_ids, randomized_host_ids.sort
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,38 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class TemplateInvocationInputTest < ActiveSupport::TestCase
4
+ let(:template) { FactoryBot.build(:job_template, :template => 'service restart <%= input("service_name") -%>') }
5
+ let(:renderer) { InputTemplateRenderer.new(template) }
6
+ let(:job_invocation) { FactoryBot.create(:job_invocation) }
7
+ let(:template_invocation) { FactoryBot.build(:template_invocation, :template => template) }
8
+ let(:result) { renderer.render }
9
+
10
+ context 'with selectable options' do
11
+ before do
12
+ result # let is lazy
13
+ template.template_inputs << FactoryBot.build(:template_input, :name => 'service_name', :input_type => 'user',
14
+ :required => true, :options => "foreman\nhttpd")
15
+ end
16
+
17
+ it 'fails with an invalid option' do
18
+ refute_valid FactoryBot.build(:template_invocation_input_value, :template_invocation => template_invocation,
19
+ :template_input => template.template_inputs.first,
20
+ :value => 'sendmail')
21
+ end
22
+
23
+ it 'succeeds with valid option' do
24
+ assert_valid FactoryBot.build(:template_invocation_input_value, :template_invocation => template_invocation,
25
+ :template_input => template.template_inputs.first,
26
+ :value => 'foreman')
27
+ end
28
+ end
29
+
30
+ it 'supports large inputs' do
31
+ template.template_inputs << FactoryBot.build(:template_input, :name => 'service_name',
32
+ :input_type => 'user', :required => true)
33
+ assert_valid FactoryBot.create(:template_invocation_input_value,
34
+ :template_invocation => template_invocation,
35
+ :template_input => template.template_inputs.first,
36
+ :value => 'foreman' * 1_000_000)
37
+ end
38
+ end