foreman_remote_execution 0.2.3 → 0.3.0

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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +7 -0
  4. data/.rubocop_todo.yml +16 -5
  5. data/.tx/config +1 -1
  6. data/app/assets/javascripts/template_invocation.js +14 -1
  7. data/app/assets/stylesheets/job_invocations.css.scss +0 -13
  8. data/app/assets/stylesheets/template_invocation.css.scss +7 -13
  9. data/app/controllers/api/v2/foreign_input_sets_controller.rb +1 -1
  10. data/app/controllers/api/v2/job_invocations_controller.rb +12 -18
  11. data/app/controllers/api/v2/remote_execution_features_controller.rb +38 -0
  12. data/app/controllers/api/v2/template_inputs_controller.rb +1 -0
  13. data/app/controllers/job_invocations_controller.rb +3 -13
  14. data/app/controllers/job_templates_controller.rb +1 -1
  15. data/app/controllers/remote_execution_features_controller.rb +19 -0
  16. data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +1 -22
  17. data/app/helpers/remote_execution_helper.rb +30 -12
  18. data/app/lib/actions/remote_execution/helpers/live_output.rb +2 -2
  19. data/app/lib/actions/remote_execution/run_host_job.rb +7 -4
  20. data/app/lib/actions/remote_execution/run_hosts_job.rb +14 -0
  21. data/app/lib/actions/remote_execution/run_proxy_command.rb +2 -2
  22. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +5 -4
  23. data/app/models/concerns/foreman_remote_execution/subnet_extensions.rb +1 -0
  24. data/app/models/input_template_renderer.rb +14 -3
  25. data/app/models/job_invocation.rb +6 -15
  26. data/app/models/job_invocation_composer.rb +131 -21
  27. data/app/models/job_template.rb +77 -22
  28. data/app/models/job_template_effective_user.rb +0 -2
  29. data/app/models/remote_execution_feature.rb +34 -0
  30. data/app/models/setting/remote_execution.rb +4 -1
  31. data/app/models/targeting.rb +2 -2
  32. data/app/models/template_input.rb +17 -13
  33. data/app/views/api/v2/remote_execution_features/base.json.rabl +3 -0
  34. data/app/views/api/v2/remote_execution_features/index.json.rabl +3 -0
  35. data/app/views/api/v2/remote_execution_features/main.json.rabl +3 -0
  36. data/app/views/api/v2/remote_execution_features/show.json.rabl +3 -0
  37. data/app/views/job_invocations/_form.html.erb +51 -40
  38. data/app/views/job_invocations/_preview_hosts_list.html.erb +1 -1
  39. data/app/views/job_invocations/_tab_hosts.html.erb +6 -3
  40. data/app/views/job_invocations/index.html.erb +11 -11
  41. data/app/views/job_templates/_custom_tabs.html.erb +4 -4
  42. data/app/views/job_templates/index.html.erb +5 -5
  43. data/app/views/overrides/nics/_execution_interface.html.erb +9 -1
  44. data/app/views/remote_execution_features/_form.html.erb +24 -0
  45. data/app/views/remote_execution_features/index.html.erb +21 -0
  46. data/app/views/remote_execution_features/show.html.erb +3 -0
  47. data/app/views/template_inputs/_form.html.erb +1 -0
  48. data/app/views/template_inputs/_invocation_form.html.erb +7 -0
  49. data/app/views/templates/package_action.erb +17 -5
  50. data/app/views/templates/power_action.erb +22 -0
  51. data/app/views/templates/puppet_run_once.erb +1 -1
  52. data/config/routes.rb +4 -0
  53. data/db/migrate/20160113162007_expand_all_template_invocations.rb +1 -1
  54. data/db/migrate/20160118124600_create_remote_execution_features.rb +14 -0
  55. data/db/migrate/20160125155108_make_job_template_name_unique.rb +12 -0
  56. data/db/migrate/20160127134031_add_advanced_to_template_input.rb +11 -0
  57. data/db/migrate/20160127162711_reword_puppet_template_description.rb +9 -0
  58. data/db/migrate/20160203104056_add_concurrency_options_to_job_invocation.rb +6 -0
  59. data/db/seeds.d/70-job_templates.rb +2 -1
  60. data/doc/plugins/div_tag.rb +1 -1
  61. data/doc/plugins/plantuml.rb +1 -1
  62. data/doc/plugins/tags.rb +7 -8
  63. data/doc/plugins/toc.rb +0 -1
  64. data/foreman_remote_execution.gemspec +1 -1
  65. data/lib/foreman_remote_execution/engine.rb +10 -2
  66. data/lib/foreman_remote_execution/version.rb +1 -1
  67. data/locale/action_names.rb +8 -0
  68. data/locale/en/foreman_remote_execution.po +767 -11
  69. data/locale/foreman_remote_execution.pot +1026 -8
  70. data/test/functional/api/v2/foreign_input_sets_controller_test.rb +1 -1
  71. data/test/functional/api/v2/job_invocations_controller_test.rb +0 -9
  72. data/test/functional/api/v2/job_templates_controller_test.rb +11 -11
  73. data/test/functional/api/v2/remote_execution_features_controller_test.rb +35 -0
  74. data/test/functional/api/v2/template_inputs_controller_test.rb +1 -1
  75. data/test/unit/actions/run_hosts_job_test.rb +48 -7
  76. data/test/unit/actions/run_proxy_command_test.rb +1 -1
  77. data/test/unit/concerns/host_extensions_test.rb +11 -0
  78. data/test/unit/input_template_renderer_test.rb +4 -4
  79. data/test/unit/job_invocation_composer_test.rb +52 -2
  80. data/test/unit/job_invocation_test.rb +1 -1
  81. data/test/unit/job_template_test.rb +126 -3
  82. data/test/unit/remote_execution_feature_test.rb +42 -0
  83. data/test/unit/targeting_test.rb +4 -4
  84. metadata +26 -7
  85. data/app/views/job_invocation_task_groups/_job_invocation_task_group.html.erb +0 -31
  86. data/app/views/unattended/snippets/_remote_execution_ssh_keys.erb +0 -18
  87. data/db/seeds.d/80-provision_templates.rb +0 -21
@@ -2,11 +2,11 @@ module Actions
2
2
  module RemoteExecution
3
3
  module Helpers
4
4
  module LiveOutput
5
- def exception_to_output(context, exception, timestamp = Time.now)
5
+ def exception_to_output(context, exception, timestamp = Time.now.getlocal)
6
6
  format_output(context + ": #{exception.class} - #{exception.message}", 'debug', timestamp)
7
7
  end
8
8
 
9
- def format_output(message, type = 'debug', timestamp = Time.now)
9
+ def format_output(message, type = 'debug', timestamp = Time.now.getlocal)
10
10
  { 'output_type' => type,
11
11
  'output' => message,
12
12
  'timestamp' => timestamp.to_f }
@@ -43,7 +43,7 @@ module Actions
43
43
  host = Host.find(input[:host][:id])
44
44
  host.refresh_statuses
45
45
  rescue => e
46
- Foreman::Logging.exception "Could not update execution status for #{input[:host][:name]}", e
46
+ ::Foreman::Logging.exception "Could not update execution status for #{input[:host][:name]}", e
47
47
  end
48
48
 
49
49
  def humanized_output
@@ -59,10 +59,13 @@ module Actions
59
59
  end
60
60
  end
61
61
 
62
+ def humanized_input
63
+ N_('%{description} on %{host}') % { :host => input[:host].try(:[], :name),
64
+ :description => input[:description].try(:capitalize) || input[:job_category] }
65
+ end
66
+
62
67
  def humanized_name
63
- _('%{description} on %{host}') % { :job_category => input[:job_category],
64
- :host => input[:host][:name],
65
- :description => input[:description].try(:capitalize) || input[:job_category] }
68
+ N_('Remote action:')
66
69
  end
67
70
 
68
71
  def find_ip_or_hostname(host)
@@ -12,6 +12,7 @@ module Actions
12
12
  end
13
13
 
14
14
  def plan(job_invocation, locked = false)
15
+ set_up_concurrency_control job_invocation
15
16
  job_invocation.task_group.save! if job_invocation.task_group.try(:new_record?)
16
17
  task.add_missing_task_groups(job_invocation.task_group) if job_invocation.task_group
17
18
  action_subject(job_invocation) unless locked
@@ -34,6 +35,11 @@ module Actions
34
35
  end
35
36
  end
36
37
 
38
+ def set_up_concurrency_control(invocation)
39
+ limit_concurrency_level invocation.concurrency_level unless invocation.concurrency_level.nil?
40
+ distribute_over_time invocation.time_span unless invocation.time_span.nil?
41
+ end
42
+
37
43
  def rescue_strategy
38
44
  ::Dynflow::Action::Rescue::Skip
39
45
  end
@@ -42,6 +48,14 @@ module Actions
42
48
  super unless event == Dynflow::Action::Skip
43
49
  end
44
50
 
51
+ def humanized_input
52
+ input[:job_invocation][:description]
53
+ end
54
+
55
+ def humanized_name
56
+ '%s:' % _(super)
57
+ end
58
+
45
59
  private
46
60
 
47
61
  def determine_proxy(template_invocation, host, load_balancer)
@@ -62,7 +62,7 @@ module Actions
62
62
  proxy_data = proxy.status_of_task(output[:proxy_task_id])['actions'].detect { |action| action['class'] == proxy_action_name }
63
63
  proxy_data.fetch('output', {}).fetch('result', [])
64
64
  rescue => e
65
- ::Foreman::Logging.exception("Failed to load data for task #{task.id} from proxy #{ input[:proxy_url] }", e)
65
+ ::Foreman::Logging.exception("Failed to load data for task #{task.id} from proxy #{input[:proxy_url]}", e)
66
66
  [exception_to_output(_('Error loading data from proxy'), e)]
67
67
  end
68
68
 
@@ -85,7 +85,7 @@ module Actions
85
85
 
86
86
  def final_timestamp(records)
87
87
  return task.ended_at if records.blank?
88
- records.last.fetch('timestamp', task.ended_at) + 1
88
+ records.last.fetch('timestamp', task.ended_at).to_f + 1
89
89
  end
90
90
 
91
91
  def proxy_result
@@ -12,7 +12,7 @@ module ForemanRemoteExecution
12
12
  has_many :template_invocations, :dependent => :destroy, :foreign_key => 'host_id'
13
13
  has_one :execution_status_object, :class_name => 'HostStatus::ExecutionStatus', :foreign_key => 'host_id'
14
14
 
15
- scoped_search :in => :execution_status_object, :on => :status, :rename => :'execution_status',
15
+ scoped_search :in => :execution_status_object, :on => :status, :rename => :execution_status,
16
16
  :complete_value => { :ok => HostStatus::ExecutionStatus::OK, :error => HostStatus::ExecutionStatus::ERROR }
17
17
  end
18
18
 
@@ -38,7 +38,7 @@ module ForemanRemoteExecution
38
38
  get_interface_by_flag(:execution)
39
39
  end
40
40
 
41
- def remote_execution_proxies(provider)
41
+ def remote_execution_proxies(provider, authorized = true)
42
42
  proxies = {}
43
43
  proxies[:subnet] = execution_interface.subnet.remote_execution_proxies.with_features(provider) if execution_interface && execution_interface.subnet
44
44
  proxies[:fallback] = smart_proxies.with_features(provider) if Setting[:remote_execution_fallback_proxy]
@@ -50,14 +50,15 @@ module ForemanRemoteExecution
50
50
  proxy_scope = ::SmartProxy
51
51
  end
52
52
 
53
- proxies[:global] = proxy_scope.authorized.with_features(provider)
53
+ proxy_scope = proxy_scope.authorized if authorized
54
+ proxies[:global] = proxy_scope.with_features(provider)
54
55
  end
55
56
 
56
57
  proxies
57
58
  end
58
59
 
59
60
  def remote_execution_ssh_keys
60
- remote_execution_proxies('SSH').values.flatten.uniq.map { |proxy| proxy.pubkey }.compact.uniq
61
+ remote_execution_proxies('SSH', false).values.flatten.uniq.map { |proxy| proxy.pubkey }.compact.uniq
61
62
  end
62
63
 
63
64
  def drop_execution_interface_cache
@@ -5,6 +5,7 @@ module ForemanRemoteExecution
5
5
  included do
6
6
  has_many :target_remote_execution_proxies, :as => :target
7
7
  has_many :remote_execution_proxies, :dependent => :destroy, :through => :target_remote_execution_proxies
8
+ attr_accessible :remote_execution_proxies, :remote_execution_proxy_ids
8
9
  end
9
10
  end
10
11
  end
@@ -2,6 +2,9 @@ class InputTemplateRenderer
2
2
  class UndefinedInput < ::Foreman::Exception
3
3
  end
4
4
 
5
+ class RenderError < ::Foreman::Exception
6
+ end
7
+
5
8
  include UnattendedHelper
6
9
 
7
10
  attr_accessor :template, :host, :invocation, :input_values, :error_message
@@ -22,7 +25,7 @@ class InputTemplateRenderer
22
25
 
23
26
  def render
24
27
  @template.validate_unique_inputs!
25
- render_safe(@template.template, ::Foreman::Renderer::ALLOWED_HELPERS + [ :input, :render_template ], :host => @host)
28
+ render_safe(@template.template, ::Foreman::Renderer::ALLOWED_HELPERS + [ :input, :render_template, :preview?, :render_error ], :host => @host)
26
29
  rescue => e
27
30
  self.error_message ||= _('error during rendering: %s') % e.message
28
31
  Rails.logger.debug e.to_s + "\n" + e.backtrace.join("\n")
@@ -48,10 +51,14 @@ class InputTemplateRenderer
48
51
  end
49
52
  end
50
53
 
54
+ def render_error(message)
55
+ raise RenderError.new(message)
56
+ end
57
+
51
58
  def render_template(template_name, input_values = {}, options = {})
52
59
  options.assert_valid_keys(:with_foreign_input_set)
53
60
  with_foreign_input_set = options.fetch(:with_foreign_input_set, true)
54
- template = JobTemplate.authorized(:view_job_templates).where(:name => template_name).first
61
+ template = JobTemplate.authorized(:view_job_templates).find_by(:name => template_name)
55
62
  unless template
56
63
  self.error_message = _('included template \'%s\' not found') % template_name
57
64
  raise error_message
@@ -69,8 +76,12 @@ class InputTemplateRenderer
69
76
  end
70
77
  end
71
78
 
79
+ def preview?
80
+ !!@preview
81
+ end
82
+
72
83
  def foreign_input_set_values(target_template, overrides = {})
73
- input_set = @template.foreign_input_sets.where(:target_template_id => target_template).first
84
+ input_set = @template.foreign_input_sets.find_by(:target_template_id => target_template)
74
85
  return overrides if input_set.nil?
75
86
 
76
87
  inputs_to_generate = input_set.inputs.map(&:name) - overrides.keys.map(&:to_s)
@@ -10,13 +10,8 @@ class JobInvocation < ActiveRecord::Base
10
10
 
11
11
  belongs_to :targeting, :dependent => :destroy
12
12
  has_many :all_template_invocations, :inverse_of => :job_invocation, :dependent => :destroy, :class_name => 'TemplateInvocation'
13
- if Rails::VERSION::MAJOR >= 4
14
- has_many :template_invocations, -> { where('host_id IS NOT NULL') }, :inverse_of => :job_invocation
15
- has_many :pattern_template_invocations, -> { where('host_id IS NULL') }, :inverse_of => :job_invocation, :class_name => 'TemplateInvocation'
16
- else
17
- has_many :template_invocations, :conditions => 'host_id IS NOT NULL', :inverse_of => :job_invocation
18
- has_many :pattern_template_invocations, :conditions => 'host_id IS NULL', :inverse_of => :job_invocation, :class_name => 'TemplateInvocation'
19
- end
13
+ has_many :template_invocations, -> { where('host_id IS NOT NULL') }, :inverse_of => :job_invocation
14
+ has_many :pattern_template_invocations, -> { where('host_id IS NULL') }, :inverse_of => :job_invocation, :class_name => 'TemplateInvocation'
20
15
 
21
16
  validates :targeting, :presence => true
22
17
  validates :job_category, :presence => true
@@ -48,11 +43,7 @@ class JobInvocation < ActiveRecord::Base
48
43
  belongs_to :triggering, :class_name => 'ForemanTasks::Triggering'
49
44
  has_one :recurring_logic, :through => :triggering, :class_name => 'ForemanTasks::RecurringLogic'
50
45
 
51
- if Rails::VERSION::MAJOR >= 4
52
- scope :with_task, -> { references(:task) }
53
- else
54
- scope :with_task, -> { joins('LEFT JOIN foreman_tasks_tasks ON foreman_tasks_tasks.id = job_invocations.task_id') }
55
- end
46
+ scope :with_task, -> { references(:task) }
56
47
 
57
48
  scoped_search :in => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id', :auto_complete => true
58
49
 
@@ -112,7 +103,7 @@ class JobInvocation < ActiveRecord::Base
112
103
  end
113
104
 
114
105
  def to_action_input
115
- { :id => id, :name => job_category }
106
+ { :id => id, :name => job_category, :description => description }
116
107
  end
117
108
 
118
109
  def failed_template_invocation_tasks
@@ -152,7 +143,7 @@ class JobInvocation < ActiveRecord::Base
152
143
  end
153
144
 
154
145
  def sub_task_for_host(host)
155
- template_invocations.where(:host => host.id).first.try(:run_host_job_task)
146
+ template_invocations.find_by(:host => host.id).try(:run_host_job_task)
156
147
  end
157
148
 
158
149
  def output(host)
@@ -165,7 +156,7 @@ class JobInvocation < ActiveRecord::Base
165
156
  template_invocation = pattern_template_invocations.first
166
157
  input_names = template_invocation.template.template_inputs_with_foreign(&:name)
167
158
  hash_base = Hash.new { |hash, key| hash[key] = "%{#{key}}" }
168
- input_hash = hash_base.merge Hash[input_names.zip(template_invocation.input_values.pluck(:value))]
159
+ input_hash = hash_base.merge Hash[input_names.zip(template_invocation.input_values.map(&:value))]
169
160
  input_hash.update(:job_category => job_category)
170
161
  input_hash.update(:template_name => template_invocation.template.name)
171
162
  description_format.scan(key_re) { |key| input_hash[key.first] }
@@ -12,6 +12,7 @@ class JobInvocationComposer
12
12
  :triggering => ui_params.fetch(:triggering, {}),
13
13
  :host_ids => ui_params[:host_ids],
14
14
  :description_format => job_invocation_base[:description_format],
15
+ :concurrency_control => concurrency_control_params,
15
16
  :template_invocations => template_invocations_params }.with_indifferent_access
16
17
  end
17
18
 
@@ -49,6 +50,13 @@ class JobInvocationComposer
49
50
  template_base
50
51
  end
51
52
  end
53
+
54
+ def concurrency_control_params
55
+ {
56
+ :time_span => job_invocation_base[:time_span],
57
+ :level => job_invocation_base[:concurrency_level]
58
+ }
59
+ end
52
60
  end
53
61
 
54
62
  class ApiParams
@@ -59,17 +67,14 @@ class JobInvocationComposer
59
67
  end
60
68
 
61
69
  def params
62
- { :job_category => job_category,
70
+ { :job_category => template.job_category,
63
71
  :targeting => targeting_params,
64
72
  :triggering => triggering_params,
65
- :description_format => api_params[:description_format] || template_with_default.generate_description_format,
73
+ :description_format => api_params[:description_format],
74
+ :concurrency_control => concurrency_control_params,
66
75
  :template_invocations => template_invocations_params }.with_indifferent_access
67
76
  end
68
77
 
69
- def job_category
70
- api_params[:job_category]
71
- end
72
-
73
78
  def targeting_params
74
79
  raise ::Foreman::Exception, _('Cannot specify both bookmark_id and search_query') if api_params[:bookmark_id] && api_params[:search_query]
75
80
  api_params.slice(:targeting_type, :bookmark_id, :search_query).merge(:user_id => User.current.id)
@@ -98,8 +103,14 @@ class JobInvocationComposer
98
103
  end
99
104
  end
100
105
 
106
+ def concurrency_control_params
107
+ {
108
+ :level => api_params.fetch(:concurrency_control, {})[:concurrency_level],
109
+ :time_span => api_params.fetch(:concurrency_control, {})[:time_span]
110
+ }
111
+ end
112
+
101
113
  def template_invocations_params
102
- template = template_with_default
103
114
  template_invocation_params = { :template_id => template.id, :effective_user => api_params[:effective_user] }
104
115
  template_invocation_params[:input_values] = api_params.fetch(:inputs, {}).map do |name, value|
105
116
  input = template.template_inputs_with_foreign.find { |i| i.name == name }
@@ -112,13 +123,8 @@ class JobInvocationComposer
112
123
  [template_invocation_params]
113
124
  end
114
125
 
115
- def template_with_default
116
- template = nil
117
- templates = JobTemplate.authorized(:view_job_templates).where(:job_category => job_category)
118
- template = templates.find(api_params[:job_template_id]) if api_params[:job_template_id]
119
- template ||= templates.first if templates.count == 1
120
- raise ::Foreman::Exception, _('Cannot determine the template to be used of job %s') % job_category unless template
121
- return template
126
+ def template
127
+ @template ||= JobTemplate.authorized(:view_job_templates).find(api_params[:job_template_id])
122
128
  end
123
129
 
124
130
  private
@@ -141,11 +147,18 @@ class JobInvocationComposer
141
147
  :targeting => targeting_params,
142
148
  :triggering => triggering_params,
143
149
  :description_format => job_invocation.description_format,
150
+ :concurrency_control => concurrency_control_params,
144
151
  :template_invocations => template_invocations_params }.with_indifferent_access
145
152
  end
146
153
 
147
154
  private
148
155
 
156
+ def concurrency_control_params
157
+ {
158
+ :level => job_invocation.concurrency_level,
159
+ :time_span => job_invocation.time_span
160
+ }
161
+ end
149
162
 
150
163
  def targeting_params
151
164
  job_invocation.targeting.attributes.slice('search_query', 'bookmark_id', 'user_id', 'targeting_type')
@@ -164,6 +177,75 @@ class JobInvocationComposer
164
177
  end
165
178
  end
166
179
 
180
+ class ParamsForFeature
181
+ attr_reader :feature_label, :feature, :provided_inputs
182
+
183
+ def initialize(feature_label, hosts, provided_inputs = {})
184
+ @feature = RemoteExecutionFeature.feature(feature_label)
185
+ @provided_inputs = provided_inputs
186
+ if hosts.is_a? Bookmark
187
+ @host_bookmark = hosts
188
+ elsif hosts.is_a? Host::Base
189
+ @host_objects = [hosts]
190
+ elsif hosts.is_a? String
191
+ @host_scoped_search = hosts
192
+ else
193
+ @host_objects = hosts
194
+ end
195
+ end
196
+
197
+ def params
198
+ { :job_category => job_template.job_category,
199
+ :targeting => targeting_params,
200
+ :triggering => {},
201
+ :concurrency_control => {},
202
+ :template_invocations => template_invocations_params }.with_indifferent_access
203
+ end
204
+
205
+ private
206
+
207
+ def targeting_params
208
+ ret = {}
209
+ ret['targeting_type'] = Targeting::STATIC_TYPE
210
+ ret['search_query'] = @host_scoped_search if @host_scoped_search
211
+ ret['search_query'] = Targeting.build_query_from_hosts(@host_objects) if @host_objects
212
+ ret['bookmark_id'] = @host_bookmark.id if @host_bookmark
213
+ ret['user_id'] = User.current.id
214
+ ret
215
+ end
216
+
217
+ def template_invocations_params
218
+ [ { 'template_id' => job_template.id,
219
+ 'input_values' => input_values_params } ]
220
+ end
221
+
222
+ def input_values_params
223
+ @provided_inputs.map do |key, value|
224
+ input = job_template.template_inputs_with_foreign.find { |i| i.name == key.to_s }
225
+ unless input
226
+ raise Foreman::Exception.new(N_('Feature input %{input_name} not defined in template %{template_name}'),
227
+ :input_name => key, :template_name => job_template.name)
228
+ end
229
+ { 'template_input_id' => input.id, 'value' => value }
230
+ end
231
+ end
232
+
233
+ def job_template
234
+ unless feature.job_template
235
+ raise Foreman::Exception.new(N_('No template mapped to feature %{feature_name}'),
236
+ :feature_name => feature.name)
237
+ end
238
+ template = JobTemplate.authorized(:view_job_templates).find_by_id(feature.job_template_id)
239
+
240
+ unless template
241
+ raise Foreman::Exception.new(N_('The template %{template_name} mapped to feature %{feature_name} is not accessible by the user'),
242
+ :template_name => template.name,
243
+ :feature_name => feature.name)
244
+ end
245
+ template
246
+ end
247
+ end
248
+
167
249
  attr_accessor :params, :job_invocation, :host_ids, :search_query
168
250
  delegate :job_category, :pattern_template_invocations, :template_invocations, :targeting, :triggering, :to => :job_invocation
169
251
 
@@ -190,6 +272,10 @@ class JobInvocationComposer
190
272
  self.new(ApiParams.new(api_params).params)
191
273
  end
192
274
 
275
+ def self.for_feature(feature_label, hosts, provided_inputs = {})
276
+ self.new(ParamsForFeature.new(feature_label, hosts, provided_inputs).params)
277
+ end
278
+
193
279
  def compose
194
280
  job_invocation.job_category = validate_job_category(params[:job_category])
195
281
  job_invocation.job_category ||= available_job_categories.first if @set_defaults
@@ -197,10 +283,26 @@ class JobInvocationComposer
197
283
  job_invocation.triggering = build_triggering
198
284
  job_invocation.pattern_template_invocations = build_template_invocations
199
285
  job_invocation.description_format = params[:description_format]
286
+ job_invocation.time_span = params[:concurrency_control][:time_span].to_i unless params[:concurrency_control][:time_span].blank?
287
+ job_invocation.concurrency_level = params[:concurrency_control][:level].to_i unless params[:concurrency_control][:level].blank?
200
288
 
201
289
  self
202
290
  end
203
291
 
292
+ def trigger(raise_on_error = false)
293
+ generate_description
294
+ if raise_on_error
295
+ save!
296
+ else
297
+ return false unless save
298
+ end
299
+ triggering.trigger(::Actions::RemoteExecution::RunHostsJob, job_invocation)
300
+ end
301
+
302
+ def trigger!
303
+ trigger(true)
304
+ end
305
+
204
306
  def valid?
205
307
  targeting.valid? & job_invocation.valid? & !pattern_template_invocations.map(&:valid?).include?(false)
206
308
  end
@@ -262,9 +364,9 @@ class JobInvocationComposer
262
364
  if @search_query.present?
263
365
  @search_query
264
366
  elsif host_ids.present?
265
- targeting.build_query_from_hosts(host_ids)
367
+ Targeting.build_query_from_hosts(host_ids)
266
368
  elsif targeting.bookmark_id
267
- if (bookmark = available_bookmarks.where(:id => targeting.bookmark_id).first)
369
+ if (bookmark = available_bookmarks.find_by(:id => targeting.bookmark_id))
268
370
  bookmark.query
269
371
  else
270
372
  ''
@@ -326,7 +428,7 @@ class JobInvocationComposer
326
428
  # when it's the same, we delete the query since it is used from bookmark
327
429
  # when no bookmark is set we store the query
328
430
  bookmark_id = params[:targeting][:bookmark_id]
329
- bookmark = available_bookmarks.where(:id => bookmark_id).first
431
+ bookmark = available_bookmarks.find_by(:id => bookmark_id)
330
432
  query = params[:targeting][:search_query]
331
433
  if bookmark.present? && query.present?
332
434
  if query.strip == bookmark.query.strip
@@ -340,10 +442,10 @@ class JobInvocationComposer
340
442
  end
341
443
 
342
444
  Targeting.new(
343
- :bookmark_id => bookmark_id,
344
- :targeting_type => params[:targeting][:targeting_type],
345
- :search_query => query
346
- ) { |t| t.user_id = params[:targeting][:user_id] }
445
+ :bookmark_id => bookmark_id,
446
+ :targeting_type => params[:targeting][:targeting_type],
447
+ :search_query => query
448
+ ) { |t| t.user_id = params[:targeting][:user_id] }
347
449
  end
348
450
 
349
451
  def build_triggering
@@ -361,6 +463,14 @@ class JobInvocationComposer
361
463
  end
362
464
  end
363
465
 
466
+ def generate_description
467
+ unless job_invocation.description_format
468
+ template = job_invocation.pattern_template_invocations.first.try(:template)
469
+ job_invocation.description_format = template.generate_description_format if template
470
+ end
471
+ job_invocation.generate_description! if job_invocation.description.blank?
472
+ end
473
+
364
474
  def build_effective_user(template_invocation_params)
365
475
  job_template = available_templates.find(template_invocation_params[:template_id])
366
476
  if job_template.effective_user.overridable? && template_invocation_params[:effective_user].present?