foreman_remote_execution 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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?
|