foreman_remote_execution 0.2.3 → 0.3.0
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/.gitignore +2 -0
- data/.rubocop.yml +7 -0
- data/.rubocop_todo.yml +16 -5
- data/.tx/config +1 -1
- data/app/assets/javascripts/template_invocation.js +14 -1
- data/app/assets/stylesheets/job_invocations.css.scss +0 -13
- data/app/assets/stylesheets/template_invocation.css.scss +7 -13
- data/app/controllers/api/v2/foreign_input_sets_controller.rb +1 -1
- data/app/controllers/api/v2/job_invocations_controller.rb +12 -18
- data/app/controllers/api/v2/remote_execution_features_controller.rb +38 -0
- data/app/controllers/api/v2/template_inputs_controller.rb +1 -0
- data/app/controllers/job_invocations_controller.rb +3 -13
- data/app/controllers/job_templates_controller.rb +1 -1
- data/app/controllers/remote_execution_features_controller.rb +19 -0
- data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +1 -22
- data/app/helpers/remote_execution_helper.rb +30 -12
- data/app/lib/actions/remote_execution/helpers/live_output.rb +2 -2
- data/app/lib/actions/remote_execution/run_host_job.rb +7 -4
- data/app/lib/actions/remote_execution/run_hosts_job.rb +14 -0
- data/app/lib/actions/remote_execution/run_proxy_command.rb +2 -2
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +5 -4
- data/app/models/concerns/foreman_remote_execution/subnet_extensions.rb +1 -0
- data/app/models/input_template_renderer.rb +14 -3
- data/app/models/job_invocation.rb +6 -15
- data/app/models/job_invocation_composer.rb +131 -21
- data/app/models/job_template.rb +77 -22
- data/app/models/job_template_effective_user.rb +0 -2
- data/app/models/remote_execution_feature.rb +34 -0
- data/app/models/setting/remote_execution.rb +4 -1
- data/app/models/targeting.rb +2 -2
- data/app/models/template_input.rb +17 -13
- data/app/views/api/v2/remote_execution_features/base.json.rabl +3 -0
- data/app/views/api/v2/remote_execution_features/index.json.rabl +3 -0
- data/app/views/api/v2/remote_execution_features/main.json.rabl +3 -0
- data/app/views/api/v2/remote_execution_features/show.json.rabl +3 -0
- data/app/views/job_invocations/_form.html.erb +51 -40
- data/app/views/job_invocations/_preview_hosts_list.html.erb +1 -1
- data/app/views/job_invocations/_tab_hosts.html.erb +6 -3
- data/app/views/job_invocations/index.html.erb +11 -11
- data/app/views/job_templates/_custom_tabs.html.erb +4 -4
- data/app/views/job_templates/index.html.erb +5 -5
- data/app/views/overrides/nics/_execution_interface.html.erb +9 -1
- data/app/views/remote_execution_features/_form.html.erb +24 -0
- data/app/views/remote_execution_features/index.html.erb +21 -0
- data/app/views/remote_execution_features/show.html.erb +3 -0
- data/app/views/template_inputs/_form.html.erb +1 -0
- data/app/views/template_inputs/_invocation_form.html.erb +7 -0
- data/app/views/templates/package_action.erb +17 -5
- data/app/views/templates/power_action.erb +22 -0
- data/app/views/templates/puppet_run_once.erb +1 -1
- data/config/routes.rb +4 -0
- data/db/migrate/20160113162007_expand_all_template_invocations.rb +1 -1
- data/db/migrate/20160118124600_create_remote_execution_features.rb +14 -0
- data/db/migrate/20160125155108_make_job_template_name_unique.rb +12 -0
- data/db/migrate/20160127134031_add_advanced_to_template_input.rb +11 -0
- data/db/migrate/20160127162711_reword_puppet_template_description.rb +9 -0
- data/db/migrate/20160203104056_add_concurrency_options_to_job_invocation.rb +6 -0
- data/db/seeds.d/70-job_templates.rb +2 -1
- data/doc/plugins/div_tag.rb +1 -1
- data/doc/plugins/plantuml.rb +1 -1
- data/doc/plugins/tags.rb +7 -8
- data/doc/plugins/toc.rb +0 -1
- data/foreman_remote_execution.gemspec +1 -1
- data/lib/foreman_remote_execution/engine.rb +10 -2
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/locale/action_names.rb +8 -0
- data/locale/en/foreman_remote_execution.po +767 -11
- data/locale/foreman_remote_execution.pot +1026 -8
- data/test/functional/api/v2/foreign_input_sets_controller_test.rb +1 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +0 -9
- data/test/functional/api/v2/job_templates_controller_test.rb +11 -11
- data/test/functional/api/v2/remote_execution_features_controller_test.rb +35 -0
- data/test/functional/api/v2/template_inputs_controller_test.rb +1 -1
- data/test/unit/actions/run_hosts_job_test.rb +48 -7
- data/test/unit/actions/run_proxy_command_test.rb +1 -1
- data/test/unit/concerns/host_extensions_test.rb +11 -0
- data/test/unit/input_template_renderer_test.rb +4 -4
- data/test/unit/job_invocation_composer_test.rb +52 -2
- data/test/unit/job_invocation_test.rb +1 -1
- data/test/unit/job_template_test.rb +126 -3
- data/test/unit/remote_execution_feature_test.rb +42 -0
- data/test/unit/targeting_test.rb +4 -4
- metadata +26 -7
- data/app/views/job_invocation_task_groups/_job_invocation_task_group.html.erb +0 -31
- data/app/views/unattended/snippets/_remote_execution_ssh_keys.erb +0 -18
- data/db/seeds.d/80-provision_templates.rb +0 -21
|
@@ -2,11 +2,11 @@ module Actions
|
|
|
2
2
|
module RemoteExecution
|
|
3
3
|
module Helpers
|
|
4
4
|
module LiveOutput
|
|
5
|
-
def exception_to_output(context, exception, timestamp = Time.now)
|
|
5
|
+
def exception_to_output(context, exception, timestamp = Time.now.getlocal)
|
|
6
6
|
format_output(context + ": #{exception.class} - #{exception.message}", 'debug', timestamp)
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def format_output(message, type = 'debug', timestamp = Time.now)
|
|
9
|
+
def format_output(message, type = 'debug', timestamp = Time.now.getlocal)
|
|
10
10
|
{ 'output_type' => type,
|
|
11
11
|
'output' => message,
|
|
12
12
|
'timestamp' => timestamp.to_f }
|
|
@@ -43,7 +43,7 @@ module Actions
|
|
|
43
43
|
host = Host.find(input[:host][:id])
|
|
44
44
|
host.refresh_statuses
|
|
45
45
|
rescue => e
|
|
46
|
-
Foreman::Logging.exception "Could not update execution status for #{input[:host][:name]}", e
|
|
46
|
+
::Foreman::Logging.exception "Could not update execution status for #{input[:host][:name]}", e
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def humanized_output
|
|
@@ -59,10 +59,13 @@ module Actions
|
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
def humanized_input
|
|
63
|
+
N_('%{description} on %{host}') % { :host => input[:host].try(:[], :name),
|
|
64
|
+
:description => input[:description].try(:capitalize) || input[:job_category] }
|
|
65
|
+
end
|
|
66
|
+
|
|
62
67
|
def humanized_name
|
|
63
|
-
|
|
64
|
-
:host => input[:host][:name],
|
|
65
|
-
:description => input[:description].try(:capitalize) || input[:job_category] }
|
|
68
|
+
N_('Remote action:')
|
|
66
69
|
end
|
|
67
70
|
|
|
68
71
|
def find_ip_or_hostname(host)
|
|
@@ -12,6 +12,7 @@ module Actions
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def plan(job_invocation, locked = false)
|
|
15
|
+
set_up_concurrency_control job_invocation
|
|
15
16
|
job_invocation.task_group.save! if job_invocation.task_group.try(:new_record?)
|
|
16
17
|
task.add_missing_task_groups(job_invocation.task_group) if job_invocation.task_group
|
|
17
18
|
action_subject(job_invocation) unless locked
|
|
@@ -34,6 +35,11 @@ module Actions
|
|
|
34
35
|
end
|
|
35
36
|
end
|
|
36
37
|
|
|
38
|
+
def set_up_concurrency_control(invocation)
|
|
39
|
+
limit_concurrency_level invocation.concurrency_level unless invocation.concurrency_level.nil?
|
|
40
|
+
distribute_over_time invocation.time_span unless invocation.time_span.nil?
|
|
41
|
+
end
|
|
42
|
+
|
|
37
43
|
def rescue_strategy
|
|
38
44
|
::Dynflow::Action::Rescue::Skip
|
|
39
45
|
end
|
|
@@ -42,6 +48,14 @@ module Actions
|
|
|
42
48
|
super unless event == Dynflow::Action::Skip
|
|
43
49
|
end
|
|
44
50
|
|
|
51
|
+
def humanized_input
|
|
52
|
+
input[:job_invocation][:description]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def humanized_name
|
|
56
|
+
'%s:' % _(super)
|
|
57
|
+
end
|
|
58
|
+
|
|
45
59
|
private
|
|
46
60
|
|
|
47
61
|
def determine_proxy(template_invocation, host, load_balancer)
|
|
@@ -62,7 +62,7 @@ module Actions
|
|
|
62
62
|
proxy_data = proxy.status_of_task(output[:proxy_task_id])['actions'].detect { |action| action['class'] == proxy_action_name }
|
|
63
63
|
proxy_data.fetch('output', {}).fetch('result', [])
|
|
64
64
|
rescue => e
|
|
65
|
-
::Foreman::Logging.exception("Failed to load data for task #{task.id} from proxy #{
|
|
65
|
+
::Foreman::Logging.exception("Failed to load data for task #{task.id} from proxy #{input[:proxy_url]}", e)
|
|
66
66
|
[exception_to_output(_('Error loading data from proxy'), e)]
|
|
67
67
|
end
|
|
68
68
|
|
|
@@ -85,7 +85,7 @@ module Actions
|
|
|
85
85
|
|
|
86
86
|
def final_timestamp(records)
|
|
87
87
|
return task.ended_at if records.blank?
|
|
88
|
-
records.last.fetch('timestamp', task.ended_at) + 1
|
|
88
|
+
records.last.fetch('timestamp', task.ended_at).to_f + 1
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def proxy_result
|
|
@@ -12,7 +12,7 @@ module ForemanRemoteExecution
|
|
|
12
12
|
has_many :template_invocations, :dependent => :destroy, :foreign_key => 'host_id'
|
|
13
13
|
has_one :execution_status_object, :class_name => 'HostStatus::ExecutionStatus', :foreign_key => 'host_id'
|
|
14
14
|
|
|
15
|
-
scoped_search :in => :execution_status_object, :on => :status, :rename => :
|
|
15
|
+
scoped_search :in => :execution_status_object, :on => :status, :rename => :execution_status,
|
|
16
16
|
:complete_value => { :ok => HostStatus::ExecutionStatus::OK, :error => HostStatus::ExecutionStatus::ERROR }
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -38,7 +38,7 @@ module ForemanRemoteExecution
|
|
|
38
38
|
get_interface_by_flag(:execution)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def remote_execution_proxies(provider)
|
|
41
|
+
def remote_execution_proxies(provider, authorized = true)
|
|
42
42
|
proxies = {}
|
|
43
43
|
proxies[:subnet] = execution_interface.subnet.remote_execution_proxies.with_features(provider) if execution_interface && execution_interface.subnet
|
|
44
44
|
proxies[:fallback] = smart_proxies.with_features(provider) if Setting[:remote_execution_fallback_proxy]
|
|
@@ -50,14 +50,15 @@ module ForemanRemoteExecution
|
|
|
50
50
|
proxy_scope = ::SmartProxy
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
proxy_scope = proxy_scope.authorized if authorized
|
|
54
|
+
proxies[:global] = proxy_scope.with_features(provider)
|
|
54
55
|
end
|
|
55
56
|
|
|
56
57
|
proxies
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
def remote_execution_ssh_keys
|
|
60
|
-
remote_execution_proxies('SSH').values.flatten.uniq.map { |proxy| proxy.pubkey }.compact.uniq
|
|
61
|
+
remote_execution_proxies('SSH', false).values.flatten.uniq.map { |proxy| proxy.pubkey }.compact.uniq
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def drop_execution_interface_cache
|
|
@@ -5,6 +5,7 @@ module ForemanRemoteExecution
|
|
|
5
5
|
included do
|
|
6
6
|
has_many :target_remote_execution_proxies, :as => :target
|
|
7
7
|
has_many :remote_execution_proxies, :dependent => :destroy, :through => :target_remote_execution_proxies
|
|
8
|
+
attr_accessible :remote_execution_proxies, :remote_execution_proxy_ids
|
|
8
9
|
end
|
|
9
10
|
end
|
|
10
11
|
end
|
|
@@ -2,6 +2,9 @@ class InputTemplateRenderer
|
|
|
2
2
|
class UndefinedInput < ::Foreman::Exception
|
|
3
3
|
end
|
|
4
4
|
|
|
5
|
+
class RenderError < ::Foreman::Exception
|
|
6
|
+
end
|
|
7
|
+
|
|
5
8
|
include UnattendedHelper
|
|
6
9
|
|
|
7
10
|
attr_accessor :template, :host, :invocation, :input_values, :error_message
|
|
@@ -22,7 +25,7 @@ class InputTemplateRenderer
|
|
|
22
25
|
|
|
23
26
|
def render
|
|
24
27
|
@template.validate_unique_inputs!
|
|
25
|
-
render_safe(@template.template, ::Foreman::Renderer::ALLOWED_HELPERS + [ :input, :render_template ], :host => @host)
|
|
28
|
+
render_safe(@template.template, ::Foreman::Renderer::ALLOWED_HELPERS + [ :input, :render_template, :preview?, :render_error ], :host => @host)
|
|
26
29
|
rescue => e
|
|
27
30
|
self.error_message ||= _('error during rendering: %s') % e.message
|
|
28
31
|
Rails.logger.debug e.to_s + "\n" + e.backtrace.join("\n")
|
|
@@ -48,10 +51,14 @@ class InputTemplateRenderer
|
|
|
48
51
|
end
|
|
49
52
|
end
|
|
50
53
|
|
|
54
|
+
def render_error(message)
|
|
55
|
+
raise RenderError.new(message)
|
|
56
|
+
end
|
|
57
|
+
|
|
51
58
|
def render_template(template_name, input_values = {}, options = {})
|
|
52
59
|
options.assert_valid_keys(:with_foreign_input_set)
|
|
53
60
|
with_foreign_input_set = options.fetch(:with_foreign_input_set, true)
|
|
54
|
-
template = JobTemplate.authorized(:view_job_templates).
|
|
61
|
+
template = JobTemplate.authorized(:view_job_templates).find_by(:name => template_name)
|
|
55
62
|
unless template
|
|
56
63
|
self.error_message = _('included template \'%s\' not found') % template_name
|
|
57
64
|
raise error_message
|
|
@@ -69,8 +76,12 @@ class InputTemplateRenderer
|
|
|
69
76
|
end
|
|
70
77
|
end
|
|
71
78
|
|
|
79
|
+
def preview?
|
|
80
|
+
!!@preview
|
|
81
|
+
end
|
|
82
|
+
|
|
72
83
|
def foreign_input_set_values(target_template, overrides = {})
|
|
73
|
-
input_set = @template.foreign_input_sets.
|
|
84
|
+
input_set = @template.foreign_input_sets.find_by(:target_template_id => target_template)
|
|
74
85
|
return overrides if input_set.nil?
|
|
75
86
|
|
|
76
87
|
inputs_to_generate = input_set.inputs.map(&:name) - overrides.keys.map(&:to_s)
|
|
@@ -10,13 +10,8 @@ class JobInvocation < ActiveRecord::Base
|
|
|
10
10
|
|
|
11
11
|
belongs_to :targeting, :dependent => :destroy
|
|
12
12
|
has_many :all_template_invocations, :inverse_of => :job_invocation, :dependent => :destroy, :class_name => 'TemplateInvocation'
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
has_many :pattern_template_invocations, -> { where('host_id IS NULL') }, :inverse_of => :job_invocation, :class_name => 'TemplateInvocation'
|
|
16
|
-
else
|
|
17
|
-
has_many :template_invocations, :conditions => 'host_id IS NOT NULL', :inverse_of => :job_invocation
|
|
18
|
-
has_many :pattern_template_invocations, :conditions => 'host_id IS NULL', :inverse_of => :job_invocation, :class_name => 'TemplateInvocation'
|
|
19
|
-
end
|
|
13
|
+
has_many :template_invocations, -> { where('host_id IS NOT NULL') }, :inverse_of => :job_invocation
|
|
14
|
+
has_many :pattern_template_invocations, -> { where('host_id IS NULL') }, :inverse_of => :job_invocation, :class_name => 'TemplateInvocation'
|
|
20
15
|
|
|
21
16
|
validates :targeting, :presence => true
|
|
22
17
|
validates :job_category, :presence => true
|
|
@@ -48,11 +43,7 @@ class JobInvocation < ActiveRecord::Base
|
|
|
48
43
|
belongs_to :triggering, :class_name => 'ForemanTasks::Triggering'
|
|
49
44
|
has_one :recurring_logic, :through => :triggering, :class_name => 'ForemanTasks::RecurringLogic'
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
scope :with_task, -> { references(:task) }
|
|
53
|
-
else
|
|
54
|
-
scope :with_task, -> { joins('LEFT JOIN foreman_tasks_tasks ON foreman_tasks_tasks.id = job_invocations.task_id') }
|
|
55
|
-
end
|
|
46
|
+
scope :with_task, -> { references(:task) }
|
|
56
47
|
|
|
57
48
|
scoped_search :in => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id', :auto_complete => true
|
|
58
49
|
|
|
@@ -112,7 +103,7 @@ class JobInvocation < ActiveRecord::Base
|
|
|
112
103
|
end
|
|
113
104
|
|
|
114
105
|
def to_action_input
|
|
115
|
-
{ :id => id, :name => job_category }
|
|
106
|
+
{ :id => id, :name => job_category, :description => description }
|
|
116
107
|
end
|
|
117
108
|
|
|
118
109
|
def failed_template_invocation_tasks
|
|
@@ -152,7 +143,7 @@ class JobInvocation < ActiveRecord::Base
|
|
|
152
143
|
end
|
|
153
144
|
|
|
154
145
|
def sub_task_for_host(host)
|
|
155
|
-
template_invocations.
|
|
146
|
+
template_invocations.find_by(:host => host.id).try(:run_host_job_task)
|
|
156
147
|
end
|
|
157
148
|
|
|
158
149
|
def output(host)
|
|
@@ -165,7 +156,7 @@ class JobInvocation < ActiveRecord::Base
|
|
|
165
156
|
template_invocation = pattern_template_invocations.first
|
|
166
157
|
input_names = template_invocation.template.template_inputs_with_foreign(&:name)
|
|
167
158
|
hash_base = Hash.new { |hash, key| hash[key] = "%{#{key}}" }
|
|
168
|
-
input_hash = hash_base.merge Hash[input_names.zip(template_invocation.input_values.
|
|
159
|
+
input_hash = hash_base.merge Hash[input_names.zip(template_invocation.input_values.map(&:value))]
|
|
169
160
|
input_hash.update(:job_category => job_category)
|
|
170
161
|
input_hash.update(:template_name => template_invocation.template.name)
|
|
171
162
|
description_format.scan(key_re) { |key| input_hash[key.first] }
|
|
@@ -12,6 +12,7 @@ class JobInvocationComposer
|
|
|
12
12
|
:triggering => ui_params.fetch(:triggering, {}),
|
|
13
13
|
:host_ids => ui_params[:host_ids],
|
|
14
14
|
:description_format => job_invocation_base[:description_format],
|
|
15
|
+
:concurrency_control => concurrency_control_params,
|
|
15
16
|
:template_invocations => template_invocations_params }.with_indifferent_access
|
|
16
17
|
end
|
|
17
18
|
|
|
@@ -49,6 +50,13 @@ class JobInvocationComposer
|
|
|
49
50
|
template_base
|
|
50
51
|
end
|
|
51
52
|
end
|
|
53
|
+
|
|
54
|
+
def concurrency_control_params
|
|
55
|
+
{
|
|
56
|
+
:time_span => job_invocation_base[:time_span],
|
|
57
|
+
:level => job_invocation_base[:concurrency_level]
|
|
58
|
+
}
|
|
59
|
+
end
|
|
52
60
|
end
|
|
53
61
|
|
|
54
62
|
class ApiParams
|
|
@@ -59,17 +67,14 @@ class JobInvocationComposer
|
|
|
59
67
|
end
|
|
60
68
|
|
|
61
69
|
def params
|
|
62
|
-
{ :job_category => job_category,
|
|
70
|
+
{ :job_category => template.job_category,
|
|
63
71
|
:targeting => targeting_params,
|
|
64
72
|
:triggering => triggering_params,
|
|
65
|
-
:description_format => api_params[:description_format]
|
|
73
|
+
:description_format => api_params[:description_format],
|
|
74
|
+
:concurrency_control => concurrency_control_params,
|
|
66
75
|
:template_invocations => template_invocations_params }.with_indifferent_access
|
|
67
76
|
end
|
|
68
77
|
|
|
69
|
-
def job_category
|
|
70
|
-
api_params[:job_category]
|
|
71
|
-
end
|
|
72
|
-
|
|
73
78
|
def targeting_params
|
|
74
79
|
raise ::Foreman::Exception, _('Cannot specify both bookmark_id and search_query') if api_params[:bookmark_id] && api_params[:search_query]
|
|
75
80
|
api_params.slice(:targeting_type, :bookmark_id, :search_query).merge(:user_id => User.current.id)
|
|
@@ -98,8 +103,14 @@ class JobInvocationComposer
|
|
|
98
103
|
end
|
|
99
104
|
end
|
|
100
105
|
|
|
106
|
+
def concurrency_control_params
|
|
107
|
+
{
|
|
108
|
+
:level => api_params.fetch(:concurrency_control, {})[:concurrency_level],
|
|
109
|
+
:time_span => api_params.fetch(:concurrency_control, {})[:time_span]
|
|
110
|
+
}
|
|
111
|
+
end
|
|
112
|
+
|
|
101
113
|
def template_invocations_params
|
|
102
|
-
template = template_with_default
|
|
103
114
|
template_invocation_params = { :template_id => template.id, :effective_user => api_params[:effective_user] }
|
|
104
115
|
template_invocation_params[:input_values] = api_params.fetch(:inputs, {}).map do |name, value|
|
|
105
116
|
input = template.template_inputs_with_foreign.find { |i| i.name == name }
|
|
@@ -112,13 +123,8 @@ class JobInvocationComposer
|
|
|
112
123
|
[template_invocation_params]
|
|
113
124
|
end
|
|
114
125
|
|
|
115
|
-
def
|
|
116
|
-
template
|
|
117
|
-
templates = JobTemplate.authorized(:view_job_templates).where(:job_category => job_category)
|
|
118
|
-
template = templates.find(api_params[:job_template_id]) if api_params[:job_template_id]
|
|
119
|
-
template ||= templates.first if templates.count == 1
|
|
120
|
-
raise ::Foreman::Exception, _('Cannot determine the template to be used of job %s') % job_category unless template
|
|
121
|
-
return template
|
|
126
|
+
def template
|
|
127
|
+
@template ||= JobTemplate.authorized(:view_job_templates).find(api_params[:job_template_id])
|
|
122
128
|
end
|
|
123
129
|
|
|
124
130
|
private
|
|
@@ -141,11 +147,18 @@ class JobInvocationComposer
|
|
|
141
147
|
:targeting => targeting_params,
|
|
142
148
|
:triggering => triggering_params,
|
|
143
149
|
:description_format => job_invocation.description_format,
|
|
150
|
+
:concurrency_control => concurrency_control_params,
|
|
144
151
|
:template_invocations => template_invocations_params }.with_indifferent_access
|
|
145
152
|
end
|
|
146
153
|
|
|
147
154
|
private
|
|
148
155
|
|
|
156
|
+
def concurrency_control_params
|
|
157
|
+
{
|
|
158
|
+
:level => job_invocation.concurrency_level,
|
|
159
|
+
:time_span => job_invocation.time_span
|
|
160
|
+
}
|
|
161
|
+
end
|
|
149
162
|
|
|
150
163
|
def targeting_params
|
|
151
164
|
job_invocation.targeting.attributes.slice('search_query', 'bookmark_id', 'user_id', 'targeting_type')
|
|
@@ -164,6 +177,75 @@ class JobInvocationComposer
|
|
|
164
177
|
end
|
|
165
178
|
end
|
|
166
179
|
|
|
180
|
+
class ParamsForFeature
|
|
181
|
+
attr_reader :feature_label, :feature, :provided_inputs
|
|
182
|
+
|
|
183
|
+
def initialize(feature_label, hosts, provided_inputs = {})
|
|
184
|
+
@feature = RemoteExecutionFeature.feature(feature_label)
|
|
185
|
+
@provided_inputs = provided_inputs
|
|
186
|
+
if hosts.is_a? Bookmark
|
|
187
|
+
@host_bookmark = hosts
|
|
188
|
+
elsif hosts.is_a? Host::Base
|
|
189
|
+
@host_objects = [hosts]
|
|
190
|
+
elsif hosts.is_a? String
|
|
191
|
+
@host_scoped_search = hosts
|
|
192
|
+
else
|
|
193
|
+
@host_objects = hosts
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def params
|
|
198
|
+
{ :job_category => job_template.job_category,
|
|
199
|
+
:targeting => targeting_params,
|
|
200
|
+
:triggering => {},
|
|
201
|
+
:concurrency_control => {},
|
|
202
|
+
:template_invocations => template_invocations_params }.with_indifferent_access
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
private
|
|
206
|
+
|
|
207
|
+
def targeting_params
|
|
208
|
+
ret = {}
|
|
209
|
+
ret['targeting_type'] = Targeting::STATIC_TYPE
|
|
210
|
+
ret['search_query'] = @host_scoped_search if @host_scoped_search
|
|
211
|
+
ret['search_query'] = Targeting.build_query_from_hosts(@host_objects) if @host_objects
|
|
212
|
+
ret['bookmark_id'] = @host_bookmark.id if @host_bookmark
|
|
213
|
+
ret['user_id'] = User.current.id
|
|
214
|
+
ret
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def template_invocations_params
|
|
218
|
+
[ { 'template_id' => job_template.id,
|
|
219
|
+
'input_values' => input_values_params } ]
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def input_values_params
|
|
223
|
+
@provided_inputs.map do |key, value|
|
|
224
|
+
input = job_template.template_inputs_with_foreign.find { |i| i.name == key.to_s }
|
|
225
|
+
unless input
|
|
226
|
+
raise Foreman::Exception.new(N_('Feature input %{input_name} not defined in template %{template_name}'),
|
|
227
|
+
:input_name => key, :template_name => job_template.name)
|
|
228
|
+
end
|
|
229
|
+
{ 'template_input_id' => input.id, 'value' => value }
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def job_template
|
|
234
|
+
unless feature.job_template
|
|
235
|
+
raise Foreman::Exception.new(N_('No template mapped to feature %{feature_name}'),
|
|
236
|
+
:feature_name => feature.name)
|
|
237
|
+
end
|
|
238
|
+
template = JobTemplate.authorized(:view_job_templates).find_by_id(feature.job_template_id)
|
|
239
|
+
|
|
240
|
+
unless template
|
|
241
|
+
raise Foreman::Exception.new(N_('The template %{template_name} mapped to feature %{feature_name} is not accessible by the user'),
|
|
242
|
+
:template_name => template.name,
|
|
243
|
+
:feature_name => feature.name)
|
|
244
|
+
end
|
|
245
|
+
template
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
167
249
|
attr_accessor :params, :job_invocation, :host_ids, :search_query
|
|
168
250
|
delegate :job_category, :pattern_template_invocations, :template_invocations, :targeting, :triggering, :to => :job_invocation
|
|
169
251
|
|
|
@@ -190,6 +272,10 @@ class JobInvocationComposer
|
|
|
190
272
|
self.new(ApiParams.new(api_params).params)
|
|
191
273
|
end
|
|
192
274
|
|
|
275
|
+
def self.for_feature(feature_label, hosts, provided_inputs = {})
|
|
276
|
+
self.new(ParamsForFeature.new(feature_label, hosts, provided_inputs).params)
|
|
277
|
+
end
|
|
278
|
+
|
|
193
279
|
def compose
|
|
194
280
|
job_invocation.job_category = validate_job_category(params[:job_category])
|
|
195
281
|
job_invocation.job_category ||= available_job_categories.first if @set_defaults
|
|
@@ -197,10 +283,26 @@ class JobInvocationComposer
|
|
|
197
283
|
job_invocation.triggering = build_triggering
|
|
198
284
|
job_invocation.pattern_template_invocations = build_template_invocations
|
|
199
285
|
job_invocation.description_format = params[:description_format]
|
|
286
|
+
job_invocation.time_span = params[:concurrency_control][:time_span].to_i unless params[:concurrency_control][:time_span].blank?
|
|
287
|
+
job_invocation.concurrency_level = params[:concurrency_control][:level].to_i unless params[:concurrency_control][:level].blank?
|
|
200
288
|
|
|
201
289
|
self
|
|
202
290
|
end
|
|
203
291
|
|
|
292
|
+
def trigger(raise_on_error = false)
|
|
293
|
+
generate_description
|
|
294
|
+
if raise_on_error
|
|
295
|
+
save!
|
|
296
|
+
else
|
|
297
|
+
return false unless save
|
|
298
|
+
end
|
|
299
|
+
triggering.trigger(::Actions::RemoteExecution::RunHostsJob, job_invocation)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def trigger!
|
|
303
|
+
trigger(true)
|
|
304
|
+
end
|
|
305
|
+
|
|
204
306
|
def valid?
|
|
205
307
|
targeting.valid? & job_invocation.valid? & !pattern_template_invocations.map(&:valid?).include?(false)
|
|
206
308
|
end
|
|
@@ -262,9 +364,9 @@ class JobInvocationComposer
|
|
|
262
364
|
if @search_query.present?
|
|
263
365
|
@search_query
|
|
264
366
|
elsif host_ids.present?
|
|
265
|
-
|
|
367
|
+
Targeting.build_query_from_hosts(host_ids)
|
|
266
368
|
elsif targeting.bookmark_id
|
|
267
|
-
if (bookmark = available_bookmarks.
|
|
369
|
+
if (bookmark = available_bookmarks.find_by(:id => targeting.bookmark_id))
|
|
268
370
|
bookmark.query
|
|
269
371
|
else
|
|
270
372
|
''
|
|
@@ -326,7 +428,7 @@ class JobInvocationComposer
|
|
|
326
428
|
# when it's the same, we delete the query since it is used from bookmark
|
|
327
429
|
# when no bookmark is set we store the query
|
|
328
430
|
bookmark_id = params[:targeting][:bookmark_id]
|
|
329
|
-
bookmark = available_bookmarks.
|
|
431
|
+
bookmark = available_bookmarks.find_by(:id => bookmark_id)
|
|
330
432
|
query = params[:targeting][:search_query]
|
|
331
433
|
if bookmark.present? && query.present?
|
|
332
434
|
if query.strip == bookmark.query.strip
|
|
@@ -340,10 +442,10 @@ class JobInvocationComposer
|
|
|
340
442
|
end
|
|
341
443
|
|
|
342
444
|
Targeting.new(
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
) { |t| t.user_id =
|
|
445
|
+
:bookmark_id => bookmark_id,
|
|
446
|
+
:targeting_type => params[:targeting][:targeting_type],
|
|
447
|
+
:search_query => query
|
|
448
|
+
) { |t| t.user_id = params[:targeting][:user_id] }
|
|
347
449
|
end
|
|
348
450
|
|
|
349
451
|
def build_triggering
|
|
@@ -361,6 +463,14 @@ class JobInvocationComposer
|
|
|
361
463
|
end
|
|
362
464
|
end
|
|
363
465
|
|
|
466
|
+
def generate_description
|
|
467
|
+
unless job_invocation.description_format
|
|
468
|
+
template = job_invocation.pattern_template_invocations.first.try(:template)
|
|
469
|
+
job_invocation.description_format = template.generate_description_format if template
|
|
470
|
+
end
|
|
471
|
+
job_invocation.generate_description! if job_invocation.description.blank?
|
|
472
|
+
end
|
|
473
|
+
|
|
364
474
|
def build_effective_user(template_invocation_params)
|
|
365
475
|
job_template = available_templates.find(template_invocation_params[:template_id])
|
|
366
476
|
if job_template.effective_user.overridable? && template_invocation_params[:effective_user].present?
|