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.
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