foreman_remote_execution 0.1.1 → 0.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/assets/javascripts/template_invocation.js +48 -5
- data/app/controllers/api/v2/job_invocations_controller.rb +55 -10
- data/app/controllers/api/v2/job_templates_controller.rb +19 -4
- data/app/controllers/api/v2/template_inputs_controller.rb +88 -0
- data/app/controllers/job_invocations_controller.rb +17 -15
- data/app/controllers/template_invocations_controller.rb +2 -0
- data/app/helpers/remote_execution_helper.rb +27 -16
- data/app/lib/actions/middleware/bind_job_invocation.rb +7 -3
- data/app/lib/actions/remote_execution/run_host_job.rb +28 -17
- data/app/lib/actions/remote_execution/run_hosts_job.rb +9 -6
- data/app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb +1 -1
- data/app/models/concerns/foreman_remote_execution/foreman_tasks_triggering_extensions.rb +9 -0
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +3 -1
- data/app/models/job_invocation.rb +48 -41
- data/app/models/job_invocation_composer.rb +205 -80
- data/app/models/job_invocation_task_group.rb +18 -0
- data/app/models/job_template.rb +25 -1
- data/app/models/job_template_effective_user.rb +23 -0
- data/app/models/remote_execution_provider.rb +25 -11
- data/app/models/setting/remote_execution.rb +6 -0
- data/app/models/ssh_execution_provider.rb +37 -0
- data/app/models/targeting.rb +13 -0
- data/app/models/template_input.rb +4 -1
- data/app/models/template_invocation.rb +23 -0
- data/app/views/api/v2/job_invocations/base.json.rabl +4 -0
- data/app/views/api/v2/job_invocations/index.json.rabl +1 -1
- data/app/views/api/v2/job_invocations/main.json.rabl +19 -0
- data/app/views/api/v2/job_invocations/show.json.rabl +0 -15
- data/app/views/api/v2/job_templates/base.json.rabl +1 -1
- data/app/views/api/v2/job_templates/index.json.rabl +1 -1
- data/app/views/api/v2/job_templates/main.json.rabl +5 -1
- data/app/views/api/v2/job_templates/show.json.rabl +4 -0
- data/app/views/api/v2/job_templates/update.json.rabl +3 -0
- data/app/views/api/v2/template_inputs/base.json.rabl +3 -0
- data/app/views/api/v2/template_inputs/create.json.rabl +3 -0
- data/app/views/api/v2/template_inputs/index.json.rabl +3 -0
- data/app/views/api/v2/template_inputs/main.json.rabl +9 -0
- data/app/views/api/v2/template_inputs/show.json.rabl +3 -0
- data/app/views/job_invocation_task_groups/_job_invocation_task_group.html.erb +31 -0
- data/app/views/job_invocation_task_groups/_job_invocation_task_groups.html.erb +3 -0
- data/app/views/job_invocations/_form.html.erb +102 -71
- data/app/views/job_invocations/_tab_overview.html.erb +5 -2
- data/app/views/job_invocations/index.html.erb +4 -4
- data/app/views/job_invocations/refresh.js.erb +2 -1
- data/app/views/job_invocations/show.html.erb +13 -2
- data/app/views/job_invocations/show.js.erb +1 -1
- data/app/views/job_templates/_custom_tabs.html.erb +16 -0
- data/app/views/templates/package_action.erb +1 -0
- data/app/views/templates/puppet_run_once.erb +1 -0
- data/app/views/templates/run_command.erb +1 -0
- data/app/views/templates/service_action.erb +1 -0
- data/config/routes.rb +15 -2
- data/db/migrate/20150923125825_add_job_invocation_task_group.rb +10 -0
- data/db/migrate/20151022105508_rename_last_task_id_column.rb +6 -0
- data/db/migrate/20151116105412_add_triggering_to_job_invocation.rb +10 -0
- data/db/migrate/20151120171100_add_effective_user_to_template_invocation.rb +5 -0
- data/db/migrate/20151124162300_create_job_template_effective_users.rb +13 -0
- data/db/migrate/20151203100824_add_description_to_job_invocation.rb +11 -0
- data/db/migrate/20151215114631_add_host_id_to_template_invocation.rb +29 -0
- data/db/migrate/20151217092555_migrate_to_task_groups.rb +16 -0
- data/foreman_remote_execution.gemspec +2 -1
- data/lib/foreman_remote_execution/engine.rb +30 -5
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/factories/foreman_remote_execution_factories.rb +5 -0
- data/test/functional/api/v2/job_invocations_controller_test.rb +3 -3
- data/test/functional/api/v2/template_inputs_controller_test.rb +61 -0
- data/test/unit/actions/run_hosts_job_test.rb +10 -3
- data/test/unit/concerns/host_extensions_test.rb +10 -6
- data/test/unit/job_invocation_composer_test.rb +229 -10
- data/test/unit/job_invocation_test.rb +27 -27
- data/test/unit/job_template_effective_user_test.rb +41 -0
- data/test/unit/job_template_test.rb +24 -0
- data/test/unit/remote_execution_provider_test.rb +39 -0
- metadata +42 -7
- data/app/models/job_invocation_api_composer.rb +0 -69
- data/test/unit/job_invocation_api_composer_test.rb +0 -143
@@ -3,18 +3,21 @@ module Actions
|
|
3
3
|
class RunHostsJob < Actions::ActionWithSubPlans
|
4
4
|
|
5
5
|
middleware.use Actions::Middleware::BindJobInvocation
|
6
|
+
middleware.use Actions::Middleware::RecurringLogic
|
6
7
|
|
7
|
-
def delay(delay_options, job_invocation)
|
8
|
+
def delay(delay_options, job_invocation, locked = false)
|
9
|
+
task.add_missing_task_groups(job_invocation.task_group)
|
8
10
|
job_invocation.targeting.resolve_hosts! if job_invocation.targeting.static?
|
9
|
-
|
10
|
-
super(delay_options, job_invocation, true)
|
11
|
+
super delay_options, job_invocation, locked
|
11
12
|
end
|
12
13
|
|
13
|
-
def plan(job_invocation, locked = false
|
14
|
+
def plan(job_invocation, locked = false)
|
15
|
+
job_invocation.task_group.save! if job_invocation.task_group.try(:new_record?)
|
16
|
+
task.add_missing_task_groups(job_invocation.task_group) if job_invocation.task_group
|
14
17
|
action_subject(job_invocation) unless locked
|
15
18
|
job_invocation.targeting.resolve_hosts! if job_invocation.targeting.dynamic? || !locked
|
16
19
|
input.update(:job_name => job_invocation.job_name)
|
17
|
-
plan_self(:job_invocation_id => job_invocation.id
|
20
|
+
plan_self(:job_invocation_id => job_invocation.id)
|
18
21
|
end
|
19
22
|
|
20
23
|
def create_sub_plans
|
@@ -24,7 +27,7 @@ module Actions
|
|
24
27
|
job_invocation.targeting.hosts.map do |host|
|
25
28
|
template_invocation = job_invocation.template_invocation_for_host(host)
|
26
29
|
proxy = determine_proxy(template_invocation, host, load_balancer)
|
27
|
-
trigger(RunHostJob, job_invocation, host, template_invocation, proxy
|
30
|
+
trigger(RunHostJob, job_invocation, host, template_invocation, proxy)
|
28
31
|
end
|
29
32
|
end
|
30
33
|
|
@@ -28,7 +28,9 @@ module ForemanRemoteExecution
|
|
28
28
|
params = params_without_remote_execution
|
29
29
|
keys = remote_execution_ssh_keys
|
30
30
|
params['remote_execution_ssh_keys'] = keys unless keys.blank?
|
31
|
-
|
31
|
+
[:remote_execution_ssh_user, :remote_execution_effective_user_method].each do |key|
|
32
|
+
params[key.to_s] = Setting[key] unless params.key?(key.to_s)
|
33
|
+
end
|
32
34
|
params
|
33
35
|
end
|
34
36
|
|
@@ -15,20 +15,43 @@ class JobInvocation < ActiveRecord::Base
|
|
15
15
|
|
16
16
|
scoped_search :on => :job_name
|
17
17
|
|
18
|
+
scoped_search :in => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id', :auto_complete => true
|
19
|
+
|
18
20
|
delegate :bookmark, :resolved?, :to => :targeting, :allow_nil => true
|
19
21
|
|
20
22
|
include ForemanTasks::Concerns::ActionSubject
|
21
23
|
|
22
|
-
belongs_to :
|
23
|
-
has_many :sub_tasks, :through => :
|
24
|
+
belongs_to :task, :class_name => 'ForemanTasks::Task'
|
25
|
+
has_many :sub_tasks, :through => :task
|
26
|
+
|
27
|
+
belongs_to :task_group, :class_name => 'JobInvocationTaskGroup'
|
28
|
+
|
29
|
+
has_many :tasks, :through => :task_group, :class_name => 'ForemanTasks::Task'
|
24
30
|
|
25
31
|
scoped_search :on => [:job_name], :complete_value => true
|
26
32
|
|
27
|
-
|
33
|
+
scoped_search :in => :task, :on => :started_at, :rename => 'started_at', :complete_value => true
|
34
|
+
scoped_search :in => :task, :on => :ended_at, :rename => 'ended_at', :complete_value => true
|
35
|
+
|
36
|
+
belongs_to :triggering, :class_name => 'ForemanTasks::Triggering'
|
37
|
+
has_one :recurring_logic, :through => :triggering, :class_name => 'ForemanTasks::RecurringLogic'
|
38
|
+
|
39
|
+
scope :with_task, -> { joins('LEFT JOIN foreman_tasks_tasks ON foreman_tasks_tasks.id = job_invocations.task_id') }
|
40
|
+
|
41
|
+
default_scope -> { order('job_invocations.id DESC') }
|
42
|
+
|
43
|
+
attr_accessor :start_before, :description_format
|
28
44
|
attr_writer :start_at
|
29
45
|
|
30
|
-
def
|
31
|
-
|
46
|
+
def deep_clone
|
47
|
+
JobInvocationComposer.from_job_invocation(self).job_invocation.tap do |invocation|
|
48
|
+
invocation.task_group = JobInvocationTaskGroup.new.tap(&:save!)
|
49
|
+
invocation.triggering = self.triggering
|
50
|
+
invocation.description_format = self.description_format
|
51
|
+
invocation.description = self.description
|
52
|
+
invocation.template_invocations = self.template_invocations.map(&:deep_clone)
|
53
|
+
invocation.save!
|
54
|
+
end
|
32
55
|
end
|
33
56
|
|
34
57
|
def to_action_input
|
@@ -63,42 +86,6 @@ class JobInvocation < ActiveRecord::Base
|
|
63
86
|
end
|
64
87
|
end
|
65
88
|
|
66
|
-
def delay_options
|
67
|
-
{
|
68
|
-
:start_at => start_at_parsed,
|
69
|
-
:start_before => start_before_parsed
|
70
|
-
}
|
71
|
-
end
|
72
|
-
|
73
|
-
def trigger_mode
|
74
|
-
@trigger_mode || :immediate
|
75
|
-
end
|
76
|
-
|
77
|
-
def trigger_mode=(value)
|
78
|
-
return trigger_mode if @trigger_mode || value.nil?
|
79
|
-
if JobInvocation.allowed_trigger_modes.include?(value)
|
80
|
-
@trigger_mode = value.to_sym
|
81
|
-
else
|
82
|
-
raise ::Foreman::Exception, _("Job Invocation trigger mode must be one of [#{JobInvocation.allowed_trigger_modes.join(', ')}]")
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def start_at_parsed
|
87
|
-
@start_at.present? && Time.strptime(@start_at, time_format)
|
88
|
-
end
|
89
|
-
|
90
|
-
def start_at
|
91
|
-
@start_at ||= Time.now.strftime(time_format)
|
92
|
-
end
|
93
|
-
|
94
|
-
def start_before_parsed
|
95
|
-
@start_before.present? && Time.strptime(@start_before, time_format) || nil
|
96
|
-
end
|
97
|
-
|
98
|
-
def time_format
|
99
|
-
'%Y-%m-%d %H:%M'
|
100
|
-
end
|
101
|
-
|
102
89
|
def template_invocation_for_host(host)
|
103
90
|
providers = available_providers(host)
|
104
91
|
providers.each do |provider|
|
@@ -118,4 +105,24 @@ class JobInvocation < ActiveRecord::Base
|
|
118
105
|
def sub_task_for_host(host)
|
119
106
|
sub_tasks.joins(:locks).where("#{ForemanTasks::Lock.table_name}.resource_type" => 'Host::Managed', "#{ForemanTasks::Lock.table_name}.resource_id" => host.id).first
|
120
107
|
end
|
108
|
+
|
109
|
+
def output(host)
|
110
|
+
return unless (task = sub_task_for_host(host)) && task.main_action && task.main_action.live_output.any?
|
111
|
+
task.main_action.live_output.first['output']
|
112
|
+
end
|
113
|
+
|
114
|
+
def generate_description!
|
115
|
+
key_re = /%\{([^\}]+)\}/
|
116
|
+
template_invocation = template_invocations.first
|
117
|
+
input_names = template_invocation.template.template_input_names
|
118
|
+
hash_base = Hash.new { |hash, key| hash[key] = "%{#{key}}" }
|
119
|
+
input_hash = hash_base.merge Hash[input_names.zip(template_invocation.input_values.pluck(:value))]
|
120
|
+
input_hash.update(:job_name => job_name)
|
121
|
+
description_format.scan(key_re) { |key| input_hash[key.first] }
|
122
|
+
self.description = description_format
|
123
|
+
input_hash.each do |k, v|
|
124
|
+
self.description.gsub!(Regexp.new("%\{#{k}\}"), v)
|
125
|
+
end
|
126
|
+
save!
|
127
|
+
end
|
121
128
|
end
|
@@ -1,38 +1,174 @@
|
|
1
1
|
class JobInvocationComposer
|
2
|
-
attr_accessor :params, :job_invocation, :host_ids, :search_query
|
3
|
-
attr_reader :job_template_ids
|
4
|
-
delegate :job_name, :targeting, :to => :job_invocation
|
5
2
|
|
6
|
-
|
7
|
-
|
3
|
+
class UiParams
|
4
|
+
attr_reader :ui_params
|
5
|
+
def initialize(ui_params)
|
6
|
+
@ui_params = ui_params
|
7
|
+
end
|
8
|
+
|
9
|
+
def params
|
10
|
+
{ :job_name => job_invocation_base[:job_name],
|
11
|
+
:targeting => ui_params.fetch(:targeting, {}).merge(:user_id => User.current.id),
|
12
|
+
:triggering => ui_params.fetch(:triggering, {}),
|
13
|
+
:host_ids => ui_params[:host_ids],
|
14
|
+
:description_format => job_invocation_base[:description_format],
|
15
|
+
:template_invocations => template_invocations_params }.with_indifferent_access
|
16
|
+
end
|
17
|
+
|
18
|
+
def job_invocation_base
|
19
|
+
ui_params.fetch(:job_invocation, {})
|
20
|
+
end
|
21
|
+
|
22
|
+
def providers_base
|
23
|
+
job_invocation_base.fetch(:providers, {})
|
24
|
+
end
|
25
|
+
|
26
|
+
# parses params to get job templates in form of id => attributes for selected job templates, e.g.
|
27
|
+
# {
|
28
|
+
# "459" => {},
|
29
|
+
# "454" => {
|
30
|
+
# "input_values" => {
|
31
|
+
# "2" => {
|
32
|
+
# "value" => ""
|
33
|
+
# },
|
34
|
+
# "5" => {
|
35
|
+
# "value" => ""
|
36
|
+
# }
|
37
|
+
# }
|
38
|
+
# }
|
39
|
+
# }
|
40
|
+
def template_invocations_params
|
41
|
+
providers_base.values.map do |template_params|
|
42
|
+
template_base = (template_params.fetch(:job_templates, {}).fetch(template_params[:job_template_id], {})).dup.with_indifferent_access
|
43
|
+
template_base[:template_id] = template_params[:job_template_id]
|
44
|
+
input_values_params = template_base.fetch(:input_values, {})
|
45
|
+
template_base[:input_values] = input_values_params.map do |id, values|
|
46
|
+
values.merge(:template_input_id => id)
|
47
|
+
end
|
48
|
+
|
49
|
+
template_base
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class ApiParams
|
55
|
+
attr_reader :api_params
|
56
|
+
|
57
|
+
def initialize(api_params)
|
58
|
+
@api_params = api_params
|
59
|
+
end
|
60
|
+
|
61
|
+
def params
|
62
|
+
{ :job_name => job_name,
|
63
|
+
:targeting => targeting_params,
|
64
|
+
:triggering => {},
|
65
|
+
:description_format => api_params[:description_format] || template_with_default.generate_description_format,
|
66
|
+
:template_invocations => template_invocations_params }.with_indifferent_access
|
67
|
+
end
|
68
|
+
|
69
|
+
def job_name
|
70
|
+
api_params[:job_name]
|
71
|
+
end
|
72
|
+
|
73
|
+
def targeting_params
|
74
|
+
raise ::Foreman::Exception, _('Cannot specify both bookmark_id and search_query') if api_params[:bookmark_id] && api_params[:search_query]
|
75
|
+
api_params.slice(:targeting_type, :bookmark_id, :search_query).merge(:user_id => User.current.id)
|
76
|
+
end
|
77
|
+
|
78
|
+
def template_invocations_params
|
79
|
+
template = template_with_default
|
80
|
+
template_invocation_params = { :template_id => template.id, :effective_user => api_params[:effective_user] }
|
81
|
+
template_invocation_params[:input_values] = api_params.fetch(:inputs, {}).map do |name, value|
|
82
|
+
input = template.template_inputs.find_by_name(name)
|
83
|
+
unless input
|
84
|
+
raise ::Foreman::Exception, _('Unknown input %{input_name} for template %{template_name}') %
|
85
|
+
{ :input_name => name, :template_name => template.name }
|
86
|
+
end
|
87
|
+
{ :template_input_id => input.id, :value => value }
|
88
|
+
end
|
89
|
+
[template_invocation_params]
|
90
|
+
end
|
91
|
+
|
92
|
+
def template_with_default
|
93
|
+
template = nil
|
94
|
+
templates = JobTemplate.authorized(:view_job_templates).where(:job_name => job_name)
|
95
|
+
template = templates.find(api_params[:job_template_id]) if api_params[:job_template_id]
|
96
|
+
template ||= templates.first if templates.count == 1
|
97
|
+
raise ::Foreman::Exception, _('Cannot determine the template to be used of job %s') % job_name unless template
|
98
|
+
return template
|
99
|
+
end
|
100
|
+
|
8
101
|
end
|
9
102
|
|
10
|
-
|
103
|
+
class ParamsFromJobInvocation
|
104
|
+
attr_reader :job_invocation
|
105
|
+
|
106
|
+
def initialize(job_invocation)
|
107
|
+
@job_invocation = job_invocation
|
108
|
+
end
|
109
|
+
|
110
|
+
def params
|
111
|
+
{ :job_name => job_invocation.job_name,
|
112
|
+
:targeting => targeting_params,
|
113
|
+
:triggering => triggering_params,
|
114
|
+
:description_format => job_invocation.description_format,
|
115
|
+
:template_invocations => template_invocations_params }.with_indifferent_access
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
|
121
|
+
def targeting_params
|
122
|
+
job_invocation.targeting.attributes.slice('search_query', 'bookmark_id', 'user_id', 'targeting_type')
|
123
|
+
end
|
124
|
+
|
125
|
+
def template_invocations_params
|
126
|
+
job_invocation.template_invocations.map do |template_invocation|
|
127
|
+
params = template_invocation.attributes.slice('template_id', 'effective_user')
|
128
|
+
params['input_values'] = template_invocation.input_values.map { |v| v.attributes.slice('template_input_id', 'value') }
|
129
|
+
params
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def triggering_params
|
134
|
+
ForemanTasks::Triggering.new_from_params.attributes.slice("mode", "start_at", "start_before")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
attr_accessor :params, :job_invocation, :host_ids, :search_query
|
139
|
+
delegate :job_name, :template_invocations, :targeting, :triggering, :to => :job_invocation
|
140
|
+
|
141
|
+
def initialize(params, set_defaults = false)
|
11
142
|
@params = params
|
143
|
+
@set_defaults = set_defaults
|
144
|
+
@job_invocation = JobInvocation.new
|
145
|
+
@job_invocation.task_group = JobInvocationTaskGroup.new
|
146
|
+
compose
|
12
147
|
|
13
148
|
@host_ids = validate_host_ids(params[:host_ids])
|
14
|
-
@search_query =
|
149
|
+
@search_query = job_invocation.targeting.search_query unless job_invocation.targeting.bookmark_id.present?
|
150
|
+
end
|
15
151
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
job_invocation.trigger_mode = job_invocation_base[:trigger_mode]
|
20
|
-
job_invocation.start_at = job_invocation_base[:start_at]
|
21
|
-
job_invocation.start_before = job_invocation_base[:start_before]
|
152
|
+
def self.from_job_invocation(job_invocation)
|
153
|
+
self.new(ParamsFromJobInvocation.new(job_invocation).params)
|
154
|
+
end
|
22
155
|
|
23
|
-
|
24
|
-
self
|
156
|
+
def self.from_ui_params(ui_params)
|
157
|
+
self.new(UiParams.new(ui_params).params, true)
|
25
158
|
end
|
26
159
|
|
27
|
-
def
|
28
|
-
|
160
|
+
def self.from_api_params(api_params)
|
161
|
+
self.new(ApiParams.new(api_params).params)
|
162
|
+
end
|
29
163
|
|
30
|
-
|
31
|
-
job_invocation.
|
32
|
-
|
164
|
+
def compose
|
165
|
+
job_invocation.job_name = validate_job_name(params[:job_name])
|
166
|
+
job_invocation.job_name ||= available_job_names.first if @set_defaults
|
167
|
+
job_invocation.targeting = build_targeting
|
168
|
+
job_invocation.triggering = build_triggering
|
169
|
+
job_invocation.template_invocations = build_template_invocations
|
170
|
+
job_invocation.description_format = params[:description_format]
|
33
171
|
|
34
|
-
@job_template_ids = invocation.template_invocations.map(&:template_id)
|
35
|
-
@template_invocations = dup_template_invocations(invocation)
|
36
172
|
self
|
37
173
|
end
|
38
174
|
|
@@ -44,6 +180,14 @@ class JobInvocationComposer
|
|
44
180
|
valid? && job_invocation.save
|
45
181
|
end
|
46
182
|
|
183
|
+
def save!
|
184
|
+
if valid?
|
185
|
+
job_invocation.save!
|
186
|
+
else
|
187
|
+
raise job_invocation.flattened_validation_exception
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
47
191
|
def available_templates
|
48
192
|
JobTemplate.authorized(:view_job_templates)
|
49
193
|
end
|
@@ -61,7 +205,7 @@ class JobInvocationComposer
|
|
61
205
|
end
|
62
206
|
|
63
207
|
def available_template_inputs
|
64
|
-
TemplateInput.where(:template_id => job_template_ids.
|
208
|
+
TemplateInput.where(:template_id => job_template_ids.empty? ? available_templates_for(job_name).map(&:id) : job_template_ids)
|
65
209
|
end
|
66
210
|
|
67
211
|
def needs_provider_type_selection?
|
@@ -89,15 +233,6 @@ class JobInvocationComposer
|
|
89
233
|
(templates_for_provider(provider_type) & selected_job_templates).empty?
|
90
234
|
end
|
91
235
|
|
92
|
-
def template_invocations
|
93
|
-
if job_invocation.new_record?
|
94
|
-
@template_invocations ||= build_template_invocations
|
95
|
-
else
|
96
|
-
job_invocation.template_invocations
|
97
|
-
# TODO update if base present? that would solve updating
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
236
|
def displayed_search_query
|
102
237
|
if @search_query.present?
|
103
238
|
@search_query
|
@@ -132,6 +267,10 @@ class JobInvocationComposer
|
|
132
267
|
0
|
133
268
|
end
|
134
269
|
|
270
|
+
def template_invocation(job_template)
|
271
|
+
template_invocations.find { |invocation| invocation.template == job_template }
|
272
|
+
end
|
273
|
+
|
135
274
|
def template_invocation_input_value_for(input)
|
136
275
|
invocations = template_invocations
|
137
276
|
default = TemplateInvocationInputValue.new
|
@@ -142,55 +281,26 @@ class JobInvocationComposer
|
|
142
281
|
end
|
143
282
|
end
|
144
283
|
|
284
|
+
def job_template_ids
|
285
|
+
job_invocation.template_invocations.map(&:template_id)
|
286
|
+
end
|
287
|
+
|
145
288
|
private
|
146
289
|
|
147
290
|
def dup_template_invocations(job_invocation)
|
148
291
|
job_invocation.template_invocations.map do |template_invocation|
|
149
292
|
duplicate = template_invocation.dup
|
150
293
|
template_invocation.input_values.map { |value| duplicate.input_values.build :value => value.value, :template_input_id => value.template_input_id }
|
294
|
+
duplicate.effective_user = template_invocation.effective_user
|
151
295
|
duplicate
|
152
296
|
end
|
153
297
|
end
|
154
298
|
|
155
|
-
def targeting_base
|
156
|
-
@params.fetch(:targeting, {})
|
157
|
-
end
|
158
|
-
|
159
|
-
def job_invocation_base
|
160
|
-
@params.fetch(:job_invocation, {})
|
161
|
-
end
|
162
|
-
|
163
|
-
def input_values_base
|
164
|
-
@params.fetch(:input_values, [])
|
165
|
-
end
|
166
|
-
|
167
|
-
def providers_base
|
168
|
-
job_invocation_base.fetch(:providers, {})
|
169
|
-
end
|
170
|
-
|
171
|
-
# parses params to get job templates in form of id => attributes for selected job templates, e.g.
|
172
|
-
# {
|
173
|
-
# "459" => {},
|
174
|
-
# "454" => {
|
175
|
-
# "input_values" => {
|
176
|
-
# "2" => {
|
177
|
-
# "value" => ""
|
178
|
-
# },
|
179
|
-
# "5" => {
|
180
|
-
# "value" => ""
|
181
|
-
# }
|
182
|
-
# }
|
183
|
-
# }
|
184
|
-
# }
|
185
|
-
def job_templates_base
|
186
|
-
Hash[providers_base.values.map { |jt| [jt['job_template_id'], (jt['job_templates'] || {})[jt['job_template_id']] || {}] }]
|
187
|
-
end
|
188
|
-
|
189
299
|
# builds input values for a given templates id based on params
|
190
300
|
# omits inputs that belongs to unavailable templates
|
191
|
-
def build_input_values_for(
|
192
|
-
|
193
|
-
input = available_template_inputs.find_by_id(
|
301
|
+
def build_input_values_for(job_template_base)
|
302
|
+
job_template_base.fetch('input_values', {}).map do |attributes|
|
303
|
+
input = available_template_inputs.find_by_id(attributes[:template_input_id])
|
194
304
|
input ? input.template_invocation_input_values.build(attributes) : nil
|
195
305
|
end.compact
|
196
306
|
end
|
@@ -199,9 +309,9 @@ class JobInvocationComposer
|
|
199
309
|
# if bookmark was used we compare it to search query,
|
200
310
|
# when it's the same, we delete the query since it is used from bookmark
|
201
311
|
# when no bookmark is set we store the query
|
202
|
-
bookmark_id =
|
312
|
+
bookmark_id = params[:targeting][:bookmark_id]
|
203
313
|
bookmark = available_bookmarks.where(:id => bookmark_id).first
|
204
|
-
query =
|
314
|
+
query = params[:targeting][:search_query]
|
205
315
|
if bookmark.present? && query.present?
|
206
316
|
if query.strip == bookmark.query.strip
|
207
317
|
query = nil
|
@@ -209,26 +319,41 @@ class JobInvocationComposer
|
|
209
319
|
bookmark_id = nil
|
210
320
|
end
|
211
321
|
elsif query.present?
|
212
|
-
query =
|
322
|
+
query = params[:targeting][:search_query]
|
213
323
|
bookmark_id = nil
|
214
324
|
end
|
215
325
|
|
216
326
|
Targeting.new(
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
327
|
+
:bookmark_id => bookmark_id,
|
328
|
+
:targeting_type => params[:targeting][:targeting_type],
|
329
|
+
:search_query => query
|
330
|
+
) { |t| t.user_id = params[:targeting][:user_id] }
|
331
|
+
end
|
332
|
+
|
333
|
+
def build_triggering
|
334
|
+
::ForemanTasks::Triggering.new_from_params(params[:triggering])
|
222
335
|
end
|
223
336
|
|
224
337
|
def build_template_invocations
|
225
|
-
|
226
|
-
|
227
|
-
|
338
|
+
valid_template_ids = validate_job_template_ids(params[:template_invocations].map { |t| t[:template_id] })
|
339
|
+
|
340
|
+
params[:template_invocations].select { |t| valid_template_ids.include?(t[:template_id].to_i) }.map do |template_invocation_params|
|
341
|
+
template_invocation = job_invocation.template_invocations.build(:template_id => template_invocation_params[:template_id],
|
342
|
+
:effective_user => build_effective_user(template_invocation_params))
|
343
|
+
template_invocation.input_values = build_input_values_for(template_invocation_params)
|
228
344
|
template_invocation
|
229
345
|
end
|
230
346
|
end
|
231
347
|
|
348
|
+
def build_effective_user(template_invocation_params)
|
349
|
+
job_template = available_templates.find(template_invocation_params[:template_id])
|
350
|
+
if job_template.effective_user.overridable? && template_invocation_params[:effective_user].present?
|
351
|
+
template_invocation_params[:effective_user]
|
352
|
+
else
|
353
|
+
job_template.effective_user.compute_value
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
232
357
|
# returns nil if user can't see any job template with such name
|
233
358
|
# existing job_name string otherwise
|
234
359
|
def validate_job_name(name)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class JobInvocationTaskGroup < ::ForemanTasks::TaskGroup
|
2
|
+
|
3
|
+
has_one :job_invocation, :foreign_key => :task_group_id
|
4
|
+
|
5
|
+
alias_method :resource, :job_invocation
|
6
|
+
|
7
|
+
def resource_name
|
8
|
+
N_('Job Invocation')
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.search_query_for(thing)
|
12
|
+
case thing
|
13
|
+
when ::ForemanTasks::RecurringLogic
|
14
|
+
"recurring_logic.id = #{thing.id}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/app/models/job_template.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
class JobTemplate < ::Template
|
2
|
-
|
2
|
+
|
3
|
+
attr_accessible :job_name, :provider_type, :description_format, :effective_user_attributes
|
3
4
|
|
4
5
|
include Authorizable
|
5
6
|
extend FriendlyId
|
@@ -31,6 +32,8 @@ class JobTemplate < ::Template
|
|
31
32
|
validates :job_name, :presence => true, :unless => ->(job_template) { job_template.snippet }
|
32
33
|
validates :provider_type, :presence => true
|
33
34
|
validate :provider_type_whitelist
|
35
|
+
has_one :effective_user, :class_name => 'JobTemplateEffectiveUser', :foreign_key => 'job_template_id', :dependent => :destroy
|
36
|
+
accepts_nested_attributes_for :effective_user, :update_only => true
|
34
37
|
|
35
38
|
# we have to override the base_class because polymorphic associations does not detect it correctly, more details at
|
36
39
|
# http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many#1010-Polymorphic-has-many-within-inherited-class-gotcha
|
@@ -77,6 +80,27 @@ class JobTemplate < ::Template
|
|
77
80
|
end
|
78
81
|
end
|
79
82
|
|
83
|
+
def provider
|
84
|
+
RemoteExecutionProvider.provider_for(provider_type)
|
85
|
+
end
|
86
|
+
|
87
|
+
def effective_user
|
88
|
+
super || (build_effective_user.tap(&:set_defaults))
|
89
|
+
end
|
90
|
+
|
91
|
+
def generate_description_format
|
92
|
+
if description_format.blank?
|
93
|
+
generated_description = '%{job_name}'
|
94
|
+
unless template_inputs.empty?
|
95
|
+
inputs = template_inputs.map(&:name).map { |name| %Q(#{name}="%{#{name}}") }.join(' ')
|
96
|
+
generated_description << " with inputs #{inputs}"
|
97
|
+
end
|
98
|
+
generated_description
|
99
|
+
else
|
100
|
+
description_format
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
80
104
|
private
|
81
105
|
|
82
106
|
def self.parse_metadata(template)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class JobTemplateEffectiveUser < ActiveRecord::Base
|
2
|
+
|
3
|
+
belongs_to :job_template
|
4
|
+
|
5
|
+
before_validation :set_defaults
|
6
|
+
|
7
|
+
belongs_to :job_template
|
8
|
+
|
9
|
+
def set_defaults
|
10
|
+
self.overridable = true if self.overridable.nil?
|
11
|
+
self.current_user = false if self.current_user.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def compute_value
|
15
|
+
if current_user?
|
16
|
+
User.current.login
|
17
|
+
elsif value.present?
|
18
|
+
value
|
19
|
+
else
|
20
|
+
Setting[:remote_execution_effective_user]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,17 +1,31 @@
|
|
1
1
|
class RemoteExecutionProvider
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
class << self
|
3
|
+
def provider_for(type)
|
4
|
+
providers[type.to_s] || providers[:Ssh]
|
5
|
+
end
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
def providers
|
8
|
+
@providers ||= { }.with_indifferent_access
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
def register(key, klass)
|
12
|
+
providers[key.to_sym] = klass
|
13
|
+
end
|
14
|
+
|
15
|
+
def provider_names
|
16
|
+
providers.keys.map(&:to_s)
|
17
|
+
end
|
18
|
+
|
19
|
+
def proxy_command_options(template_invocation, host)
|
20
|
+
{}
|
21
|
+
end
|
22
|
+
|
23
|
+
def humanized_name
|
24
|
+
self.name
|
25
|
+
end
|
13
26
|
|
14
|
-
|
15
|
-
|
27
|
+
def supports_effective_user?
|
28
|
+
false
|
29
|
+
end
|
16
30
|
end
|
17
31
|
end
|