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.
- checksums.yaml +4 -4
- data/app/models/job_template.rb +1 -0
- data/app/views/template_invocations/show.js.erb +1 -1
- data/db/migrate/20240312133027_extend_template_invocation_events.rb +9 -0
- data/lib/foreman_remote_execution/engine.rb +1 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/lib/tasks/foreman_remote_execution_tasks.rake +3 -0
- data/test/benchmark/run_hosts_job_benchmark.rb +70 -0
- data/test/benchmark/targeting_benchmark.rb +31 -0
- data/test/factories/foreman_remote_execution_factories.rb +147 -0
- data/test/functional/api/v2/foreign_input_sets_controller_test.rb +58 -0
- data/test/functional/api/v2/job_invocations_controller_test.rb +446 -0
- data/test/functional/api/v2/job_templates_controller_test.rb +110 -0
- data/test/functional/api/v2/registration_controller_test.rb +73 -0
- data/test/functional/api/v2/remote_execution_features_controller_test.rb +34 -0
- data/test/functional/api/v2/template_invocations_controller_test.rb +33 -0
- data/test/functional/cockpit_controller_test.rb +16 -0
- data/test/functional/job_invocations_controller_test.rb +132 -0
- data/test/functional/job_templates_controller_test.rb +31 -0
- data/test/functional/ui_job_wizard_controller_test.rb +16 -0
- data/test/graphql/mutations/job_invocations/create_test.rb +58 -0
- data/test/graphql/queries/job_invocation_query_test.rb +31 -0
- data/test/graphql/queries/job_invocations_query_test.rb +35 -0
- data/test/helpers/remote_execution_helper_test.rb +46 -0
- data/test/support/remote_execution_helper.rb +5 -0
- data/test/test_plugin_helper.rb +9 -0
- data/test/unit/actions/run_host_job_test.rb +115 -0
- data/test/unit/actions/run_hosts_job_test.rb +214 -0
- data/test/unit/api_params_test.rb +25 -0
- data/test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb +29 -0
- data/test/unit/concerns/host_extensions_test.rb +219 -0
- data/test/unit/concerns/nic_extensions_test.rb +9 -0
- data/test/unit/execution_task_status_mapper_test.rb +92 -0
- data/test/unit/input_template_renderer_test.rb +503 -0
- data/test/unit/job_invocation_composer_test.rb +974 -0
- data/test/unit/job_invocation_report_template_test.rb +60 -0
- data/test/unit/job_invocation_test.rb +232 -0
- data/test/unit/job_template_effective_user_test.rb +37 -0
- data/test/unit/job_template_test.rb +316 -0
- data/test/unit/remote_execution_feature_test.rb +86 -0
- data/test/unit/remote_execution_provider_test.rb +298 -0
- data/test/unit/renderer_scope_input_test.rb +49 -0
- data/test/unit/targeting_test.rb +206 -0
- data/test/unit/template_invocation_input_value_test.rb +38 -0
- 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
|