foreman_remote_execution 3.0.2 → 3.2.2
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 +4 -4
- data/.hound.yml +2 -1
- data/.rubocop.yml +80 -50
- data/.rubocop_todo.yml +113 -73
- data/Gemfile +4 -0
- data/app/controllers/api/v2/foreign_input_sets_controller.rb +3 -2
- data/app/controllers/api/v2/job_invocations_controller.rb +12 -7
- data/app/controllers/api/v2/job_templates_controller.rb +3 -2
- data/app/controllers/api/v2/remote_execution_features_controller.rb +3 -2
- data/app/controllers/api/v2/template_invocations_controller.rb +5 -2
- data/app/controllers/cockpit_controller.rb +1 -0
- data/app/controllers/concerns/foreman/controller/parameters/foreign_input_set.rb +1 -1
- data/app/controllers/concerns/foreman/controller/parameters/job_template.rb +4 -4
- data/app/controllers/job_invocations_controller.rb +10 -6
- data/app/controllers/job_templates_controller.rb +1 -1
- data/app/controllers/remote_execution_features_controller.rb +3 -2
- data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +16 -5
- data/app/helpers/job_invocations_chart_helper.rb +13 -10
- data/app/helpers/job_invocations_helper.rb +19 -6
- data/app/helpers/remote_execution_helper.rb +49 -48
- data/app/lib/actions/remote_execution/run_host_job.rb +5 -6
- data/app/lib/actions/remote_execution/run_hosts_job.rb +2 -2
- data/app/lib/foreman_remote_execution/renderer/scope/input.rb +1 -0
- data/app/lib/proxy_api/remote_execution_ssh.rb +6 -0
- data/app/models/concerns/foreman_remote_execution/errors_flattener.rb +0 -2
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +4 -6
- data/app/models/concerns/foreman_remote_execution/nic_extensions.rb +1 -0
- data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +63 -0
- data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +5 -0
- data/app/models/foreign_input_set.rb +3 -2
- data/app/models/host_status/execution_status.rb +9 -1
- data/app/models/input_template_renderer.rb +1 -1
- data/app/models/job_invocation.rb +10 -12
- data/app/models/job_invocation_composer.rb +20 -14
- data/app/models/job_invocation_task_group.rb +1 -1
- data/app/models/job_template.rb +3 -3
- data/app/models/remote_execution_feature.rb +0 -2
- data/app/models/remote_execution_provider.rb +4 -2
- data/app/models/setting/remote_execution.rb +54 -56
- data/app/models/ssh_execution_provider.rb +2 -2
- data/app/models/targeting.rb +1 -0
- data/app/models/template_invocation.rb +2 -3
- data/app/views/api/v2/job_invocations/base.json.rabl +1 -1
- data/app/views/api/v2/job_invocations/main.json.rabl +7 -4
- data/app/views/job_invocations/_card_results.html.erb +1 -0
- data/app/views/job_invocations/_card_target_hosts.html.erb +12 -0
- data/app/views/job_invocations/_card_user_input.html.erb +1 -1
- data/app/views/job_invocations/_form.html.erb +3 -2
- data/app/views/job_invocations/_host_status_td.html.erb +1 -1
- data/app/views/job_invocations/_rerun_taxonomies.html.erb +22 -0
- data/app/views/job_invocations/_tab_overview.html.erb +1 -1
- data/app/views/job_invocations/_user_input.html.erb +1 -1
- data/app/views/job_invocations/show.html.erb +3 -1
- data/config/routes.rb +2 -1
- data/db/migrate/20151215114631_add_host_id_to_template_invocation.rb +1 -0
- data/db/migrate/20180110104432_rename_template_invocation_permission.rb +1 -0
- data/db/seeds.d/50-notification_blueprints.rb +4 -4
- data/db/seeds.d/90-bookmarks.rb +1 -0
- data/extra/cockpit/foreman-cockpit-session +7 -2
- data/foreman_remote_execution.gemspec +1 -1
- data/lib/foreman_remote_execution/engine.rb +20 -17
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/locale/action_names.rb +4 -3
- data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/de/foreman_remote_execution.po +65 -16
- data/locale/en/foreman_remote_execution.po +63 -15
- data/locale/en_GB/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/en_GB/foreman_remote_execution.po +64 -15
- data/locale/es/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/es/foreman_remote_execution.po +66 -17
- data/locale/foreman_remote_execution.pot +193 -148
- data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/fr/foreman_remote_execution.po +66 -17
- data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ja/foreman_remote_execution.po +66 -17
- data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ko/foreman_remote_execution.po +65 -15
- data/locale/pt_BR/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/pt_BR/foreman_remote_execution.po +66 -17
- data/locale/ru/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ru/foreman_remote_execution.po +65 -18
- data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_CN/foreman_remote_execution.po +66 -17
- data/locale/zh_TW/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_TW/foreman_remote_execution.po +65 -16
- data/package.json +2 -3
- data/test/benchmark/run_hosts_job_benchmark.rb +1 -1
- data/test/factories/foreman_remote_execution_factories.rb +1 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +51 -23
- data/test/functional/api/v2/job_templates_controller_test.rb +1 -1
- data/test/functional/api/v2/remote_execution_features_controller_test.rb +2 -2
- data/test/functional/api/v2/template_invocations_controller_test.rb +4 -4
- data/test/functional/job_invocations_controller_test.rb +23 -11
- data/test/functional/job_templates_controller_test.rb +1 -1
- data/test/models/orchestration/ssh_test.rb +56 -0
- data/test/unit/actions/run_hosts_job_test.rb +8 -8
- data/test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb +3 -3
- data/test/unit/concerns/host_extensions_test.rb +26 -19
- data/test/unit/concerns/nic_extensions_test.rb +1 -1
- data/test/unit/execution_task_status_mapper_test.rb +10 -10
- data/test/unit/input_template_renderer_test.rb +77 -77
- data/test/unit/job_invocation_composer_test.rb +100 -96
- data/test/unit/job_invocation_test.rb +29 -29
- data/test/unit/job_template_effective_user_test.rb +3 -3
- data/test/unit/job_template_test.rb +31 -31
- data/test/unit/remote_execution_feature_test.rb +19 -19
- data/test/unit/remote_execution_provider_test.rb +33 -30
- data/test/unit/renderer_scope_input.rb +6 -6
- data/test/unit/targeting_test.rb +6 -6
- data/test/unit/template_invocation_input_value_test.rb +3 -3
- data/webpack/index.js +0 -15
- metadata +8 -4
|
@@ -42,7 +42,8 @@ module Actions
|
|
|
42
42
|
:script => script,
|
|
43
43
|
:execution_timeout_interval => job_invocation.execution_timeout_interval,
|
|
44
44
|
:secrets => secrets(host, job_invocation, provider),
|
|
45
|
-
:use_batch_triggering => true
|
|
45
|
+
:use_batch_triggering => true,
|
|
46
|
+
:use_concurrency_control => options[:use_concurrency_control]}
|
|
46
47
|
action_options = provider.proxy_command_options(template_invocation, host)
|
|
47
48
|
.merge(additional_options)
|
|
48
49
|
|
|
@@ -94,7 +95,6 @@ module Actions
|
|
|
94
95
|
super << self
|
|
95
96
|
end
|
|
96
97
|
|
|
97
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
98
98
|
def fill_continuous_output(continuous_output)
|
|
99
99
|
delegated_output.fetch('result', []).each do |raw_output|
|
|
100
100
|
continuous_output.add_raw_output(raw_output)
|
|
@@ -109,13 +109,12 @@ module Actions
|
|
|
109
109
|
end
|
|
110
110
|
if exit_status
|
|
111
111
|
continuous_output.add_output(_('Exit status: %s') % exit_status, 'stdout', final_timestamp)
|
|
112
|
-
elsif run_step
|
|
112
|
+
elsif run_step&.error
|
|
113
113
|
continuous_output.add_output(_('Job finished with error') + ": #{run_step.error.exception_class} - #{run_step.error.message}", 'debug', final_timestamp)
|
|
114
114
|
end
|
|
115
115
|
rescue => e
|
|
116
116
|
continuous_output.add_exception(_('Error loading data from proxy'), e)
|
|
117
117
|
end
|
|
118
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
119
118
|
|
|
120
119
|
def exit_status
|
|
121
120
|
delegated_output[:exit_status]
|
|
@@ -162,8 +161,8 @@ module Actions
|
|
|
162
161
|
offline_proxies = proxy_selector.offline
|
|
163
162
|
settings = { :count => offline_proxies.count, :proxy_names => offline_proxies.map(&:name).join(', ') }
|
|
164
163
|
raise n_('The only applicable proxy %{proxy_names} is down',
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
'All %{count} applicable proxies are down. Tried %{proxy_names}',
|
|
165
|
+
offline_proxies.count) % settings
|
|
167
166
|
elsif proxy == :not_defined
|
|
168
167
|
settings = { :global_proxy => 'remote_execution_global_proxy',
|
|
169
168
|
:fallback_proxy => 'remote_execution_fallback_proxy' }
|
|
@@ -42,7 +42,7 @@ module Actions
|
|
|
42
42
|
# during actual run (here) so we build template invocations from these patterns
|
|
43
43
|
template_invocation = job_invocation.pattern_template_invocation_for_host(host).deep_clone
|
|
44
44
|
template_invocation.host_id = host.id
|
|
45
|
-
trigger(RunHostJob, job_invocation, host, template_invocation, proxy_selector)
|
|
45
|
+
trigger(RunHostJob, job_invocation, host, template_invocation, proxy_selector, { :use_concurrency_control => uses_concurrency_control })
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -76,7 +76,7 @@ module Actions
|
|
|
76
76
|
limit_concurrency_level invocation.concurrency_level unless invocation.concurrency_level.nil?
|
|
77
77
|
unless invocation.time_span.nil?
|
|
78
78
|
distribute_over_time(invocation.time_span,
|
|
79
|
-
|
|
79
|
+
invocation.targeting.hosts.count)
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
82
|
|
|
@@ -61,6 +61,7 @@ module ForemanRemoteExecution
|
|
|
61
61
|
|
|
62
62
|
def input(name)
|
|
63
63
|
return template_input_values[name.to_s] if template_input_values.key?(name.to_s)
|
|
64
|
+
|
|
64
65
|
input = find_by_name(template.template_inputs_with_foreign, name) # rubocop:disable Rails/DynamicFindBy
|
|
65
66
|
if input
|
|
66
67
|
@preview ? input.preview(self) : input.value(self)
|
|
@@ -10,5 +10,11 @@ module ::ProxyAPI
|
|
|
10
10
|
rescue => e
|
|
11
11
|
raise ProxyException.new(url, e, N_('Unable to fetch public key'))
|
|
12
12
|
end
|
|
13
|
+
|
|
14
|
+
def drop_from_known_hosts(hostname)
|
|
15
|
+
delete('known_hosts/' + hostname)
|
|
16
|
+
rescue => e
|
|
17
|
+
raise ProxyException.new(url, e, N_('Unable to remove host from known hosts'))
|
|
18
|
+
end
|
|
13
19
|
end
|
|
14
20
|
end
|
|
@@ -7,7 +7,6 @@ module ForemanRemoteExecution
|
|
|
7
7
|
def flattened_errors
|
|
8
8
|
errors = Hash.new { |h, k| h[k] = [] }
|
|
9
9
|
# self.errors is ActiveModel::Errors, not Hash and doesn't have the #each_key method
|
|
10
|
-
# rubocop:disable Performance/HashEachMethods
|
|
11
10
|
self.errors.keys.each do |key|
|
|
12
11
|
messages = self.errors[key]
|
|
13
12
|
invalid_objects = invalid_objects_for_attribute(key)
|
|
@@ -19,7 +18,6 @@ module ForemanRemoteExecution
|
|
|
19
18
|
end
|
|
20
19
|
end
|
|
21
20
|
end
|
|
22
|
-
# rubocop:enable Performance/HashEachMethods
|
|
23
21
|
errors.map { |key, messages| self.errors.full_message(key, messages.join(', ')) }
|
|
24
22
|
end
|
|
25
23
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module ForemanRemoteExecution
|
|
2
2
|
module HostExtensions
|
|
3
|
-
# rubocop:disable Metrics/BlockLength
|
|
4
3
|
def self.prepended(base)
|
|
5
4
|
base.instance_eval do
|
|
6
5
|
has_many :targeting_hosts, :dependent => :destroy, :foreign_key => 'host_id'
|
|
@@ -27,11 +26,11 @@ module ForemanRemoteExecution
|
|
|
27
26
|
|
|
28
27
|
mapping = {
|
|
29
28
|
'job_invocation.id' => %(#{TemplateInvocation.table_name}.job_invocation_id #{operator} ?),
|
|
30
|
-
'job_invocation.result' => %(#{ForemanTasks::Task.table_name}.result #{operator} (?))
|
|
29
|
+
'job_invocation.result' => %(#{ForemanTasks::Task.table_name}.result #{operator} (?)),
|
|
31
30
|
}
|
|
32
31
|
{
|
|
33
32
|
:conditions => sanitize_sql_for_conditions([mapping[key], value_to_sql(operator, value)]),
|
|
34
|
-
:joins => { :template_invocations => [:run_host_job_task] }
|
|
33
|
+
:joins => { :template_invocations => [:run_host_job_task] },
|
|
35
34
|
}
|
|
36
35
|
end
|
|
37
36
|
end
|
|
@@ -50,7 +49,7 @@ module ForemanRemoteExecution
|
|
|
50
49
|
keys = remote_execution_ssh_keys
|
|
51
50
|
source = 'global'
|
|
52
51
|
if keys.present?
|
|
53
|
-
value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| v
|
|
52
|
+
value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| [v].flatten.compact }
|
|
54
53
|
params['remote_execution_ssh_keys'] = {:value => value + keys, :safe_value => safe_value + keys, :source => source}
|
|
55
54
|
end
|
|
56
55
|
[:remote_execution_ssh_user, :remote_execution_effective_user_method,
|
|
@@ -65,10 +64,9 @@ module ForemanRemoteExecution
|
|
|
65
64
|
get_interface_by_flag(:execution)
|
|
66
65
|
end
|
|
67
66
|
|
|
68
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
|
69
67
|
def remote_execution_proxies(provider, authorized = true)
|
|
70
68
|
proxies = {}
|
|
71
|
-
proxies[:subnet] = execution_interface.subnet.remote_execution_proxies.with_features(provider) if execution_interface
|
|
69
|
+
proxies[:subnet] = execution_interface.subnet.remote_execution_proxies.with_features(provider) if execution_interface&.subnet
|
|
72
70
|
proxies[:fallback] = smart_proxies.with_features(provider) if Setting[:remote_execution_fallback_proxy]
|
|
73
71
|
|
|
74
72
|
if Setting[:remote_execution_global_proxy]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module ForemanRemoteExecution
|
|
2
|
+
module Orchestration::SSH
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
before_destroy :ssh_destroy
|
|
7
|
+
after_validation :queue_ssh_destroy
|
|
8
|
+
register_rebuild(:queue_ssh_destroy, N_("SSH_#{self.to_s.split('::').first}"))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def drop_from_known_hosts(proxy_id)
|
|
12
|
+
_, _, target = host_kind_target
|
|
13
|
+
return true if target.nil?
|
|
14
|
+
|
|
15
|
+
proxy = ::SmartProxy.find(proxy_id)
|
|
16
|
+
begin
|
|
17
|
+
proxy.drop_host_from_known_hosts(target)
|
|
18
|
+
rescue RestClient::ResourceNotFound => e
|
|
19
|
+
# ignore 404 when known_hosts entry is missing or the module was not enabled
|
|
20
|
+
Foreman::Logging.exception "Proxy failed to delete SSH known_hosts for #{name}, #{ip}", e, :level => :error
|
|
21
|
+
rescue => e
|
|
22
|
+
Rails.logger.warn e.message
|
|
23
|
+
return false
|
|
24
|
+
end
|
|
25
|
+
true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def ssh_destroy
|
|
29
|
+
logger.debug "Scheduling SSH known_hosts cleanup"
|
|
30
|
+
|
|
31
|
+
host, _kind, _target = host_kind_target
|
|
32
|
+
proxies = host.remote_execution_proxies('SSH').values
|
|
33
|
+
proxies.flatten.uniq.each do |proxy|
|
|
34
|
+
queue.create(id: queue_id(proxy.id), name: _("Remove SSH known hosts for %s") % self,
|
|
35
|
+
priority: 200, action: [self, :drop_from_known_hosts, proxy.id])
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def queue_ssh_destroy
|
|
40
|
+
should_drop_from_known_hosts? && ssh_destroy
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def should_drop_from_known_hosts?
|
|
44
|
+
host, = host_kind_target
|
|
45
|
+
host && !host.new_record? && host.build && host.changes.key?('build')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def host_kind_target
|
|
51
|
+
if self.is_a?(::Host::Base)
|
|
52
|
+
[self, 'host', name]
|
|
53
|
+
else
|
|
54
|
+
[self.host, 'interface', ip]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def queue_id(proxy_id)
|
|
59
|
+
_, kind, id = host_kind_target
|
|
60
|
+
"ssh_remove_known_hosts_#{kind}_#{id}_#{proxy_id}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -6,11 +6,16 @@ module ForemanRemoteExecution
|
|
|
6
6
|
|
|
7
7
|
def update_pubkey
|
|
8
8
|
return unless has_feature?('SSH')
|
|
9
|
+
|
|
9
10
|
key = ::ProxyAPI::RemoteExecutionSSH.new(:url => url).pubkey
|
|
10
11
|
self.update_attribute(:pubkey, key) if key
|
|
11
12
|
key
|
|
12
13
|
end
|
|
13
14
|
|
|
15
|
+
def drop_host_from_known_hosts(host)
|
|
16
|
+
::ProxyAPI::RemoteExecutionSSH.new(:url => url).drop_from_known_hosts(host)
|
|
17
|
+
end
|
|
18
|
+
|
|
14
19
|
def refresh
|
|
15
20
|
errors = super
|
|
16
21
|
update_pubkey
|
|
@@ -16,9 +16,10 @@ class ForeignInputSet < ApplicationRecord
|
|
|
16
16
|
|
|
17
17
|
def inputs(templates_stack = [])
|
|
18
18
|
return [] unless target_template
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
if templates_stack.include?(target_template) || template&.id == target_template&.id
|
|
20
21
|
raise CircularDependencyError.new(N_("Circular dependency detected in foreign input set '%{template}' -> '%{target_template}'. Templates stack: %{templates_stack}"),
|
|
21
|
-
|
|
22
|
+
:template => template.name, :target_template => target_template.name, :templates_stack => templates_stack.map(&:name).inspect)
|
|
22
23
|
end
|
|
23
24
|
inputs = target_template.template_inputs_with_foreign(templates_stack + [target_template])
|
|
24
25
|
unless self.include_all?
|
|
@@ -7,8 +7,10 @@ class HostStatus::ExecutionStatus < HostStatus::Status
|
|
|
7
7
|
QUEUED = 2
|
|
8
8
|
# execution is in progress, dynflow task was created
|
|
9
9
|
RUNNING = 3
|
|
10
|
+
# execution has been cancelled
|
|
11
|
+
CANCELLED = 4
|
|
10
12
|
# mapping to string representation
|
|
11
|
-
STATUS_NAMES = { OK => 'succeeded', ERROR => 'failed', QUEUED => 'queued', RUNNING => 'running' }.freeze
|
|
13
|
+
STATUS_NAMES = { OK => 'succeeded', ERROR => 'failed', QUEUED => 'queued', RUNNING => 'running', CANCELLED => 'cancelled' }.freeze
|
|
12
14
|
|
|
13
15
|
def relevant?(*args)
|
|
14
16
|
execution_tasks.present?
|
|
@@ -38,6 +40,8 @@ class HostStatus::ExecutionStatus < HostStatus::Status
|
|
|
38
40
|
case to_status(options)
|
|
39
41
|
when OK
|
|
40
42
|
execution_tasks.present? ? N_('Last execution succeeded') : N_('No execution finished yet')
|
|
43
|
+
when CANCELLED
|
|
44
|
+
N_('Last execution cancelled')
|
|
41
45
|
when ERROR
|
|
42
46
|
N_('Last execution failed')
|
|
43
47
|
else
|
|
@@ -54,6 +58,8 @@ class HostStatus::ExecutionStatus < HostStatus::Status
|
|
|
54
58
|
case status
|
|
55
59
|
when OK
|
|
56
60
|
[ "state = 'stopped' AND result = 'success'" ]
|
|
61
|
+
when CANCELLED
|
|
62
|
+
[ "state = 'stopped' AND result = 'cancelled'" ]
|
|
57
63
|
when ERROR
|
|
58
64
|
[ "state = 'stopped' AND (result = 'error' OR result = 'warning')" ]
|
|
59
65
|
when QUEUED
|
|
@@ -74,6 +80,8 @@ class HostStatus::ExecutionStatus < HostStatus::Status
|
|
|
74
80
|
QUEUED
|
|
75
81
|
elsif task.state == 'stopped' && task.result == 'success'
|
|
76
82
|
OK
|
|
83
|
+
elsif task.state == 'stopped' && task.result == 'cancelled'
|
|
84
|
+
CANCELLED
|
|
77
85
|
elsif task.pending?
|
|
78
86
|
RUNNING
|
|
79
87
|
else
|
|
@@ -43,7 +43,7 @@ class InputTemplateRenderer
|
|
|
43
43
|
input_values: @template_input_values,
|
|
44
44
|
templates_stack: templates_stack,
|
|
45
45
|
input_template_instance: self,
|
|
46
|
-
current_user: User.current.try(:login)
|
|
46
|
+
current_user: User.current.try(:login),
|
|
47
47
|
}
|
|
48
48
|
)
|
|
49
49
|
Foreman::Renderer.render(source, @scope)
|
|
@@ -10,7 +10,7 @@ class JobInvocation < ApplicationRecord
|
|
|
10
10
|
FLATTENED_ERRORS_MAPPING = {
|
|
11
11
|
:pattern_template_invocations => lambda do |template_invocation|
|
|
12
12
|
_('template') + " #{template_invocation.template.name}"
|
|
13
|
-
end
|
|
13
|
+
end,
|
|
14
14
|
}.freeze
|
|
15
15
|
|
|
16
16
|
belongs_to :targeting, :dependent => :destroy
|
|
@@ -198,23 +198,21 @@ class JobInvocation < ApplicationRecord
|
|
|
198
198
|
|
|
199
199
|
def output(host)
|
|
200
200
|
return unless (task = sub_task_for_host(host)) && task.main_action && task.main_action.live_output.any?
|
|
201
|
+
|
|
201
202
|
task.main_action.live_output.first['output']
|
|
202
203
|
end
|
|
203
204
|
|
|
204
205
|
def generate_description
|
|
205
|
-
key_re = /%\{([^\}]+)\}/
|
|
206
206
|
template_invocation = pattern_template_invocations.first
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
input_hash.update(:job_category => job_category)
|
|
212
|
-
input_hash.update(:template_name => template_invocation.template.name)
|
|
213
|
-
description_format.scan(key_re) { |key| input_hash[key.first] }
|
|
214
|
-
self.description = description_format
|
|
215
|
-
input_hash.each do |k, v|
|
|
216
|
-
self.description.gsub!(Regexp.new("%\{#{k}\}"), v || '')
|
|
207
|
+
input_hash = template_invocation.input_values.reduce({}) do |h, v|
|
|
208
|
+
value = v.value
|
|
209
|
+
value = '*' * 3 if v.template_input.respond_to?(:hidden_value) && v.template_input.hidden_value?
|
|
210
|
+
h.update("%{#{v.template_input.name}}" => value)
|
|
217
211
|
end
|
|
212
|
+
input_hash.update("%{job_category}" => job_category)
|
|
213
|
+
input_hash.update("%{template_name}" => template_invocation.template.name)
|
|
214
|
+
input_hash.default = "''"
|
|
215
|
+
self.description = description_format.gsub(/%{[^}]+}/) { |key| input_hash[key] }
|
|
218
216
|
self.description = self.description[0..(JobInvocation.columns_hash['description'].limit - 1)]
|
|
219
217
|
end
|
|
220
218
|
|
|
@@ -37,7 +37,7 @@ class JobInvocationComposer
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def blank_to_nil(thing)
|
|
40
|
-
thing.
|
|
40
|
+
thing.presence
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# TODO: Fix this comment
|
|
@@ -71,16 +71,19 @@ class JobInvocationComposer
|
|
|
71
71
|
def concurrency_control_params
|
|
72
72
|
{
|
|
73
73
|
:time_span => job_invocation_base[:time_span],
|
|
74
|
-
:level => job_invocation_base[:concurrency_level]
|
|
74
|
+
:level => job_invocation_base[:concurrency_level],
|
|
75
75
|
}
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def triggering
|
|
79
79
|
return {} unless ui_params.key?(:triggering)
|
|
80
|
+
|
|
80
81
|
trig = ui_params[:triggering]
|
|
81
82
|
keys = (1..5).map { |i| "end_time(#{i}i)" }
|
|
82
|
-
|
|
83
|
-
trig
|
|
83
|
+
values = trig.fetch(:end_time, {}).values_at(*keys)
|
|
84
|
+
return trig if values.any?(&:nil?)
|
|
85
|
+
|
|
86
|
+
trig.merge(:end_time => Time.local(*values))
|
|
84
87
|
end
|
|
85
88
|
|
|
86
89
|
def targeting(targeting_params)
|
|
@@ -108,6 +111,7 @@ class JobInvocationComposer
|
|
|
108
111
|
|
|
109
112
|
def targeting_params
|
|
110
113
|
raise ::Foreman::Exception, _('Cannot specify both bookmark_id and search_query') if api_params[:bookmark_id] && api_params[:search_query]
|
|
114
|
+
|
|
111
115
|
api_params.slice(:targeting_type, :bookmark_id, :search_query, :randomized_ordering).merge(:user_id => User.current.id)
|
|
112
116
|
end
|
|
113
117
|
|
|
@@ -120,14 +124,14 @@ class JobInvocationComposer
|
|
|
120
124
|
:cronline => api_params[:recurrence][:cron_line],
|
|
121
125
|
:end_time => format_datetime(api_params[:recurrence][:end_time]),
|
|
122
126
|
:input_type => :cronline,
|
|
123
|
-
:max_iteration => api_params[:recurrence][:max_iteration]
|
|
127
|
+
:max_iteration => api_params[:recurrence][:max_iteration],
|
|
124
128
|
}
|
|
125
129
|
elsif api_params[:scheduling].present?
|
|
126
130
|
{
|
|
127
131
|
:mode => :future,
|
|
128
132
|
:start_at_raw => format_datetime(api_params[:scheduling][:start_at]),
|
|
129
133
|
:start_before_raw => format_datetime(api_params[:scheduling][:start_before]),
|
|
130
|
-
:end_time_limited => api_params[:scheduling][:start_before] ? true : false
|
|
134
|
+
:end_time_limited => api_params[:scheduling][:start_before] ? true : false,
|
|
131
135
|
}
|
|
132
136
|
else
|
|
133
137
|
{}
|
|
@@ -137,7 +141,7 @@ class JobInvocationComposer
|
|
|
137
141
|
def concurrency_control_params
|
|
138
142
|
{
|
|
139
143
|
:level => api_params.fetch(:concurrency_control, {})[:concurrency_level],
|
|
140
|
-
:time_span => api_params.fetch(:concurrency_control, {})[:time_span]
|
|
144
|
+
:time_span => api_params.fetch(:concurrency_control, {})[:time_span],
|
|
141
145
|
}
|
|
142
146
|
end
|
|
143
147
|
|
|
@@ -162,6 +166,7 @@ class JobInvocationComposer
|
|
|
162
166
|
|
|
163
167
|
def format_datetime(datetime)
|
|
164
168
|
return datetime if datetime.blank?
|
|
169
|
+
|
|
165
170
|
Time.parse(datetime).strftime('%Y-%m-%d %H:%M')
|
|
166
171
|
end
|
|
167
172
|
end
|
|
@@ -195,7 +200,7 @@ class JobInvocationComposer
|
|
|
195
200
|
def concurrency_control_params
|
|
196
201
|
{
|
|
197
202
|
:level => job_invocation.concurrency_level,
|
|
198
|
-
:time_span => job_invocation.time_span
|
|
203
|
+
:time_span => job_invocation.time_span,
|
|
199
204
|
}
|
|
200
205
|
end
|
|
201
206
|
|
|
@@ -271,11 +276,12 @@ class JobInvocationComposer
|
|
|
271
276
|
|
|
272
277
|
def input_values_params
|
|
273
278
|
return {} if @provided_inputs.blank?
|
|
279
|
+
|
|
274
280
|
@provided_inputs.map do |key, value|
|
|
275
281
|
input = job_template.template_inputs_with_foreign.find { |i| i.name == key.to_s }
|
|
276
282
|
unless input
|
|
277
283
|
raise Foreman::Exception.new(N_('Feature input %{input_name} not defined in template %{template_name}'),
|
|
278
|
-
|
|
284
|
+
:input_name => key, :template_name => job_template.name)
|
|
279
285
|
end
|
|
280
286
|
{ 'template_input_id' => input.id, 'value' => value }
|
|
281
287
|
end
|
|
@@ -284,14 +290,14 @@ class JobInvocationComposer
|
|
|
284
290
|
def job_template
|
|
285
291
|
unless feature.job_template
|
|
286
292
|
raise Foreman::Exception.new(N_('No template mapped to feature %{feature_name}'),
|
|
287
|
-
|
|
293
|
+
:feature_name => feature.name)
|
|
288
294
|
end
|
|
289
295
|
template = JobTemplate.authorized(:view_job_templates).find_by(id: feature.job_template_id)
|
|
290
296
|
|
|
291
297
|
unless template
|
|
292
298
|
raise Foreman::Exception.new(N_('The template %{template_name} mapped to feature %{feature_name} is not accessible by the user'),
|
|
293
|
-
|
|
294
|
-
|
|
299
|
+
:template_name => template.name,
|
|
300
|
+
:feature_name => feature.name)
|
|
295
301
|
end
|
|
296
302
|
template
|
|
297
303
|
end
|
|
@@ -329,7 +335,6 @@ class JobInvocationComposer
|
|
|
329
335
|
self.new(ParamsForFeature.new(feature_label, hosts, provided_inputs).params)
|
|
330
336
|
end
|
|
331
337
|
|
|
332
|
-
# rubocop:disable Metrics/AbcSize
|
|
333
338
|
def compose
|
|
334
339
|
job_invocation.job_category = validate_job_category(params[:job_category])
|
|
335
340
|
job_invocation.job_category ||= resolve_job_category(available_job_categories.first) { |tempate| template.job_category } if @set_defaults
|
|
@@ -354,7 +359,6 @@ class JobInvocationComposer
|
|
|
354
359
|
|
|
355
360
|
self
|
|
356
361
|
end
|
|
357
|
-
# rubocop:enable Metrics/AbcSize
|
|
358
362
|
|
|
359
363
|
def trigger(raise_on_error = false)
|
|
360
364
|
generate_description
|
|
@@ -576,8 +580,10 @@ class JobInvocationComposer
|
|
|
576
580
|
def resolve_for_composer(default_value, &block)
|
|
577
581
|
setting_value = Setting['remote_execution_form_job_template']
|
|
578
582
|
return default_value unless setting_value
|
|
583
|
+
|
|
579
584
|
form_template = JobTemplate.find_by :name => setting_value
|
|
580
585
|
return default_value unless form_template
|
|
586
|
+
|
|
581
587
|
if block_given?
|
|
582
588
|
yield form_template
|
|
583
589
|
else
|