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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/template_invocation.js +48 -5
  3. data/app/controllers/api/v2/job_invocations_controller.rb +55 -10
  4. data/app/controllers/api/v2/job_templates_controller.rb +19 -4
  5. data/app/controllers/api/v2/template_inputs_controller.rb +88 -0
  6. data/app/controllers/job_invocations_controller.rb +17 -15
  7. data/app/controllers/template_invocations_controller.rb +2 -0
  8. data/app/helpers/remote_execution_helper.rb +27 -16
  9. data/app/lib/actions/middleware/bind_job_invocation.rb +7 -3
  10. data/app/lib/actions/remote_execution/run_host_job.rb +28 -17
  11. data/app/lib/actions/remote_execution/run_hosts_job.rb +9 -6
  12. data/app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb +1 -1
  13. data/app/models/concerns/foreman_remote_execution/foreman_tasks_triggering_extensions.rb +9 -0
  14. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +3 -1
  15. data/app/models/job_invocation.rb +48 -41
  16. data/app/models/job_invocation_composer.rb +205 -80
  17. data/app/models/job_invocation_task_group.rb +18 -0
  18. data/app/models/job_template.rb +25 -1
  19. data/app/models/job_template_effective_user.rb +23 -0
  20. data/app/models/remote_execution_provider.rb +25 -11
  21. data/app/models/setting/remote_execution.rb +6 -0
  22. data/app/models/ssh_execution_provider.rb +37 -0
  23. data/app/models/targeting.rb +13 -0
  24. data/app/models/template_input.rb +4 -1
  25. data/app/models/template_invocation.rb +23 -0
  26. data/app/views/api/v2/job_invocations/base.json.rabl +4 -0
  27. data/app/views/api/v2/job_invocations/index.json.rabl +1 -1
  28. data/app/views/api/v2/job_invocations/main.json.rabl +19 -0
  29. data/app/views/api/v2/job_invocations/show.json.rabl +0 -15
  30. data/app/views/api/v2/job_templates/base.json.rabl +1 -1
  31. data/app/views/api/v2/job_templates/index.json.rabl +1 -1
  32. data/app/views/api/v2/job_templates/main.json.rabl +5 -1
  33. data/app/views/api/v2/job_templates/show.json.rabl +4 -0
  34. data/app/views/api/v2/job_templates/update.json.rabl +3 -0
  35. data/app/views/api/v2/template_inputs/base.json.rabl +3 -0
  36. data/app/views/api/v2/template_inputs/create.json.rabl +3 -0
  37. data/app/views/api/v2/template_inputs/index.json.rabl +3 -0
  38. data/app/views/api/v2/template_inputs/main.json.rabl +9 -0
  39. data/app/views/api/v2/template_inputs/show.json.rabl +3 -0
  40. data/app/views/job_invocation_task_groups/_job_invocation_task_group.html.erb +31 -0
  41. data/app/views/job_invocation_task_groups/_job_invocation_task_groups.html.erb +3 -0
  42. data/app/views/job_invocations/_form.html.erb +102 -71
  43. data/app/views/job_invocations/_tab_overview.html.erb +5 -2
  44. data/app/views/job_invocations/index.html.erb +4 -4
  45. data/app/views/job_invocations/refresh.js.erb +2 -1
  46. data/app/views/job_invocations/show.html.erb +13 -2
  47. data/app/views/job_invocations/show.js.erb +1 -1
  48. data/app/views/job_templates/_custom_tabs.html.erb +16 -0
  49. data/app/views/templates/package_action.erb +1 -0
  50. data/app/views/templates/puppet_run_once.erb +1 -0
  51. data/app/views/templates/run_command.erb +1 -0
  52. data/app/views/templates/service_action.erb +1 -0
  53. data/config/routes.rb +15 -2
  54. data/db/migrate/20150923125825_add_job_invocation_task_group.rb +10 -0
  55. data/db/migrate/20151022105508_rename_last_task_id_column.rb +6 -0
  56. data/db/migrate/20151116105412_add_triggering_to_job_invocation.rb +10 -0
  57. data/db/migrate/20151120171100_add_effective_user_to_template_invocation.rb +5 -0
  58. data/db/migrate/20151124162300_create_job_template_effective_users.rb +13 -0
  59. data/db/migrate/20151203100824_add_description_to_job_invocation.rb +11 -0
  60. data/db/migrate/20151215114631_add_host_id_to_template_invocation.rb +29 -0
  61. data/db/migrate/20151217092555_migrate_to_task_groups.rb +16 -0
  62. data/foreman_remote_execution.gemspec +2 -1
  63. data/lib/foreman_remote_execution/engine.rb +30 -5
  64. data/lib/foreman_remote_execution/version.rb +1 -1
  65. data/test/factories/foreman_remote_execution_factories.rb +5 -0
  66. data/test/functional/api/v2/job_invocations_controller_test.rb +3 -3
  67. data/test/functional/api/v2/template_inputs_controller_test.rb +61 -0
  68. data/test/unit/actions/run_hosts_job_test.rb +10 -3
  69. data/test/unit/concerns/host_extensions_test.rb +10 -6
  70. data/test/unit/job_invocation_composer_test.rb +229 -10
  71. data/test/unit/job_invocation_test.rb +27 -27
  72. data/test/unit/job_template_effective_user_test.rb +41 -0
  73. data/test/unit/job_template_test.rb +24 -0
  74. data/test/unit/remote_execution_provider_test.rb +39 -0
  75. metadata +42 -7
  76. data/app/models/job_invocation_api_composer.rb +0 -69
  77. 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
- action_subject(job_invocation)
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, connection_options = {})
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, :connection_options => connection_options)
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, input[:connection_options])
30
+ trigger(RunHostJob, job_invocation, host, template_invocation, proxy)
28
31
  end
29
32
  end
30
33
 
@@ -3,7 +3,7 @@ module ForemanRemoteExecution
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- has_many :job_invocations, :dependent => :nullify, :foreign_key => 'last_task_id'
6
+ has_many :job_invocations, :dependent => :nullify, :foreign_key => 'task_id'
7
7
  end
8
8
  end
9
9
  end
@@ -0,0 +1,9 @@
1
+ module ForemanRemoteExecution
2
+ module ForemanTasksTriggeringExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_one :job_invocation, :dependent => :nullify, :foreign_key => 'triggering_id'
7
+ end
8
+ end
9
+ end
@@ -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
- params['remote_execution_ssh_user'] = Setting[:remote_execution_ssh_user] unless params.key?('remote_execution_ssh_user')
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 :last_task, :class_name => 'ForemanTasks::Task'
23
- has_many :sub_tasks, :through => :last_task
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
- attr_accessor :start_before
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 self.allowed_trigger_modes
31
- %w(immediate future)
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
- def initialize(job_invocation = JobInvocation.new)
7
- @job_invocation = job_invocation
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
- def compose_from_params(params)
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 = targeting_base[:search_query]
149
+ @search_query = job_invocation.targeting.search_query unless job_invocation.targeting.bookmark_id.present?
150
+ end
15
151
 
16
- job_invocation.job_name = validate_job_name(job_invocation_base[:job_name])
17
- job_invocation.job_name ||= available_job_names.first if job_invocation.new_record?
18
- job_invocation.targeting = build_targeting
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
- @job_template_ids = validate_job_template_ids(job_templates_base.keys.compact)
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 compose_from_invocation(invocation)
28
- @params = {}
160
+ def self.from_api_params(api_params)
161
+ self.new(ApiParams.new(api_params).params)
162
+ end
29
163
 
30
- job_invocation.job_name = validate_job_name(invocation.job_name)
31
- job_invocation.targeting = invocation.targeting.dup
32
- @search_query = targeting.search_query unless targeting.bookmark_id.present?
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.nil? ? available_templates_for(job_name).map(&: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(job_template_id)
192
- job_templates_base[job_template_id.to_s].fetch('input_values', {}).map do |input_id, attributes|
193
- input = available_template_inputs.find_by_id(input_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 = targeting_base[:bookmark_id]
312
+ bookmark_id = params[:targeting][:bookmark_id]
203
313
  bookmark = available_bookmarks.where(:id => bookmark_id).first
204
- query = targeting_base[:search_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 = targeting_base[:search_query]
322
+ query = params[:targeting][:search_query]
213
323
  bookmark_id = nil
214
324
  end
215
325
 
216
326
  Targeting.new(
217
- :user => User.current,
218
- :bookmark_id => bookmark_id,
219
- :targeting_type => targeting_base[:targeting_type],
220
- :search_query => query
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
- job_template_ids.map do |job_template_id|
226
- template_invocation = job_invocation.template_invocations.build(:template_id => job_template_id)
227
- template_invocation.input_values = build_input_values_for(job_template_id)
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
@@ -1,5 +1,6 @@
1
1
  class JobTemplate < ::Template
2
- attr_accessible :job_name, :provider_type
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
- def self.provider_for(type)
3
- providers[type.to_s] || providers[:Ssh]
4
- end
2
+ class << self
3
+ def provider_for(type)
4
+ providers[type.to_s] || providers[:Ssh]
5
+ end
5
6
 
6
- def self.providers
7
- @providers ||= { :Ssh => N_(SSHExecutionProvider) }.with_indifferent_access
8
- end
7
+ def providers
8
+ @providers ||= { }.with_indifferent_access
9
+ end
9
10
 
10
- def self.register(key, klass)
11
- providers[key.to_sym] = klass
12
- end
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
- def self.provider_names
15
- providers.keys.map(&:to_s)
27
+ def supports_effective_user?
28
+ false
29
+ end
16
30
  end
17
31
  end