foreman_remote_execution 0.1.1 → 0.1.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/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
|