foreman_remote_execution 1.4.5 → 1.4.6

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 (42) hide show
  1. checksums.yaml +5 -5
  2. data/.hound.yml +13 -0
  3. data/.rubocop.yml +9 -0
  4. data/app/controllers/api/v2/job_invocations_controller.rb +49 -6
  5. data/app/controllers/job_invocations_controller.rb +29 -1
  6. data/app/helpers/remote_execution_helper.rb +5 -5
  7. data/app/lib/actions/remote_execution/run_host_job.rb +32 -20
  8. data/app/lib/actions/remote_execution/run_hosts_job.rb +13 -8
  9. data/app/models/foreign_input_set.rb +1 -1
  10. data/app/models/job_invocation.rb +23 -1
  11. data/app/models/job_invocation_composer.rb +25 -3
  12. data/app/models/job_template.rb +2 -2
  13. data/app/models/remote_execution_feature.rb +22 -10
  14. data/app/models/setting/remote_execution.rb +34 -10
  15. data/app/models/ssh_execution_provider.rb +8 -0
  16. data/app/models/template_input.rb +1 -1
  17. data/app/views/api/v2/job_templates/base.json.rabl +1 -1
  18. data/app/views/job_invocations/_form.html.erb +11 -5
  19. data/app/views/job_invocations/_tab_hosts.html.erb +1 -1
  20. data/app/views/job_invocations/index.html.erb +1 -1
  21. data/app/views/job_templates/index.html.erb +1 -1
  22. data/app/views/remote_execution_features/index.html.erb +1 -1
  23. data/app/views/template_inputs/_invocation_form.html.erb +2 -2
  24. data/app/views/template_invocations/show.html.erb +1 -1
  25. data/config/routes.rb +5 -0
  26. data/db/migrate/20160113162007_expand_all_template_invocations.rb +1 -1
  27. data/db/migrate/20171129103615_add_secrets_to_job_invocations.rb +6 -0
  28. data/db/migrate/20180202072115_add_notification_builder_to_remote_execution_feature.rb +5 -0
  29. data/db/migrate/20180202123215_add_feature_id_to_job_invocation.rb +6 -0
  30. data/db/migrate/20180226095631_change_task_id_to_uuid.rb +31 -0
  31. data/foreman_remote_execution.gemspec +1 -1
  32. data/lib/foreman_remote_execution/engine.rb +3 -1
  33. data/lib/foreman_remote_execution/version.rb +1 -1
  34. data/test/factories/foreman_remote_execution_factories.rb +6 -0
  35. data/test/functional/api/v2/job_invocations_controller_test.rb +139 -32
  36. data/test/functional/job_invocations_controller_test.rb +49 -0
  37. data/test/unit/actions/run_hosts_job_test.rb +10 -6
  38. data/test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb +1 -1
  39. data/test/unit/job_invocation_composer_test.rb +43 -1
  40. metadata +14 -10
  41. data/app/models/concerns/foreman_remote_execution/exportable.rb +0 -71
  42. data/test/unit/concerns/exportable_test.rb +0 -88
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e7507b3bd9447227e181965ced4a856671c6ad7d
4
- data.tar.gz: a93d632086110ccc09d8c02d77c26ce28ba1ac75
2
+ SHA256:
3
+ metadata.gz: 3a0c3ef241880bf26fcf76f004b430ad451324b4d544302a423f92f61ed354c9
4
+ data.tar.gz: 3fd717b03faf2baa425c23150cba6aee3ecd0025d39a1233eab8b27307141d03
5
5
  SHA512:
6
- metadata.gz: 882b0bc570bdcadaf26301d1be84ece8a3833ded6b9d5bba8c0da16cf8e4bb2c411e295700b0425fcfda3865baf111585e1224d16ab0c77599c4ac790440a284
7
- data.tar.gz: f9b9a0eb4e2df30f472cfa0d4a9b8b48ab2e74424c24bd0247a29fe478b65c8178e015c12c023f6a5f8c18aa24dfbc00653a5e4a8a9cc4a7ac85ae6f80fcddd8
6
+ metadata.gz: 65801bb36a9f54ae4c01bab9b4926e28066f7b5f0e608bfeff0f858262d6d432d9c380c5c2b982ea2d5375649fdcfe8d8fce485b820af6091b01b30323120f07
7
+ data.tar.gz: e37a034355a6967cc5e760e37299de3ba0afed9845715ae4628e83665606b1e21f558fb677392cee93b396036a420d48fbaaee1b6f5397d75a3a645dd6a39c34
data/.hound.yml ADDED
@@ -0,0 +1,13 @@
1
+ scss:
2
+ enabled: false
3
+
4
+ stylelint:
5
+ enabled: true
6
+
7
+ ruby:
8
+ config_file: .rubocop.yml
9
+
10
+ jshint:
11
+ enabled: false
12
+
13
+ fail_on_violations: true
data/.rubocop.yml CHANGED
@@ -91,5 +91,14 @@ Naming/HeredocDelimiterNaming:
91
91
  Style/RescueStandardError:
92
92
  Enabled: false
93
93
 
94
+ Style/SafeNavigation:
95
+ Enabled: false
96
+
94
97
  Lint/BooleanSymbol:
95
98
  Enabled: false
99
+
100
+ Metrics/PerceivedComplexity:
101
+ Max: 8
102
+
103
+ Metrics/AbcSize:
104
+ Max: 45
@@ -6,8 +6,7 @@ module Api
6
6
 
7
7
  before_action :find_optional_nested_object
8
8
  before_action :find_host, :only => %w{output}
9
- before_action :find_resource, :only => %w{show update destroy clone}
10
- before_action :validate_template, :only => :create
9
+ before_action :find_resource, :only => %w{show update destroy clone cancel rerun}
11
10
 
12
11
  wrap_parameters JobInvocation, :include => (JobInvocation.attribute_names + [:ssh])
13
12
 
@@ -24,7 +23,7 @@ module Api
24
23
  # rubocop:disable Metrics/BlockLength
25
24
  def_param_group :job_invocation do
26
25
  param :job_invocation, Hash, :required => true, :action_aware => true do
27
- param :job_template_id, String, :required => true, :desc => N_('The job template to use')
26
+ param :job_template_id, String, :required => false, :desc => N_('The job template to use, parameter is required unless feature was specified')
28
27
  param :targeting_type, String, :required => true, :desc => N_('Invocation type, one of %s') % Targeting::TYPES
29
28
  param :inputs, Hash, :required => false, :desc => N_('Inputs to use')
30
29
  param :ssh, Hash, :desc => N_('SSH provider specific options') do
@@ -50,16 +49,24 @@ module Api
50
49
  end
51
50
 
52
51
  param :bookmark_id, Integer, :required => false
53
- param :search_query, Integer, :required => false
52
+ param :search_query, String, :required => false
54
53
  param :description_format, String, :required => false, :desc => N_('Override the description format from the template for this invocation only')
55
54
  param :execution_timeout_interval, Integer, :required => false, :desc => N_('Override the timeout interval from the template for this invocation only')
55
+ param :feature, String, :required => false, :desc => N_('Remote execution feature label that should be triggered, job template assigned to this feature will be used')
56
56
  end
57
57
  end
58
58
 
59
59
  api :POST, '/job_invocations/', N_('Create a job invocation')
60
60
  param_group :job_invocation, :as => :create
61
61
  def create
62
- composer = JobInvocationComposer.from_api_params(job_invocation_params)
62
+ if job_invocation_params[:feature].present?
63
+ composer = composer_for_feature
64
+ else
65
+ validate_template
66
+ composer = JobInvocationComposer.from_api_params(
67
+ job_invocation_params
68
+ )
69
+ end
63
70
  composer.trigger!
64
71
  @job_invocation = composer.job_invocation
65
72
  process_response @job_invocation
@@ -88,12 +95,39 @@ module Api
88
95
  end
89
96
  end
90
97
 
98
+ api :POST, '/job_invocations/:id/cancel', N_('Cancel job invocation')
99
+ param :id, :identifier, :required => true
100
+ param :force, :bool
101
+ def cancel
102
+ if @job_invocation.task.cancellable?
103
+ result = @job_invocation.cancel(params.fetch('force', false))
104
+ render :json => { :cancelled => result, :id => @job_invocation.id }
105
+ else
106
+ render :json => { :message => _('The job could not be cancelled.') },
107
+ :status => 422
108
+ end
109
+ end
110
+
111
+ api :POST, '/job_invocations/:id/rerun', N_('Rerun job on failed hosts')
112
+ param :id, :identifier, :required => true
113
+ param :failed_only, :bool
114
+ def rerun
115
+ composer = JobInvocationComposer.from_job_invocation(@job_invocation, params)
116
+ composer.trigger!
117
+ @job_invocation = composer.job_invocation
118
+ process_response @job_invocation
119
+ end
120
+
91
121
  private
92
122
 
93
123
  def action_permission
94
124
  case params[:action]
95
125
  when 'output'
96
126
  :view
127
+ when 'cancel'
128
+ :cancel
129
+ when 'rerun'
130
+ :create
97
131
  else
98
132
  super
99
133
  end
@@ -112,11 +146,20 @@ module Api
112
146
  end
113
147
 
114
148
  def job_invocation_params
149
+ return @job_invocation_params if @job_invocation_params.present?
115
150
  job_invocation_params = params.fetch(:job_invocation, {}).dup
116
151
  job_invocation_params.merge!(job_invocation_params.delete(:ssh)) if job_invocation_params.key?(:ssh)
117
152
  job_invocation_params[:inputs] ||= {}
118
153
  job_invocation_params[:inputs].permit!
119
- job_invocation_params
154
+ @job_invocation_params = job_invocation_params
155
+ end
156
+
157
+ def composer_for_feature
158
+ JobInvocationComposer.for_feature(
159
+ job_invocation_params[:feature],
160
+ job_invocation_params[:host_ids],
161
+ job_invocation_params[:inputs].to_hash
162
+ )
120
163
  end
121
164
  end
122
165
  end
@@ -3,6 +3,7 @@ class JobInvocationsController < ApplicationController
3
3
  include ::ForemanTasks::Concerns::Parameters::Triggering
4
4
 
5
5
  def new
6
+ return @composer = prepare_composer if params[:feature].present?
6
7
  ui_params = {
7
8
  :host_ids => params[:host_ids],
8
9
  :targeting => {
@@ -72,6 +73,26 @@ class JobInvocationsController < ApplicationController
72
73
  render :partial => 'job_invocations/preview_hosts_list'
73
74
  end
74
75
 
76
+ def cancel
77
+ @job_invocation = resource_base.find(params[:id])
78
+ result = @job_invocation.cancel(params[:force])
79
+
80
+ if result
81
+ flash[:notice] = if params[:force]
82
+ _('Trying to abort the job')
83
+ else
84
+ _('Trying to cancel the job')
85
+ end
86
+ else
87
+ flash[:warning] = if params[:force]
88
+ _('The job cannot be aborted at the moment.')
89
+ else
90
+ _('The job cannot be cancelled at the moment.')
91
+ end
92
+ end
93
+ redirect_back(:fallback_location => job_invocation_path(@job_invocation))
94
+ end
95
+
75
96
  private
76
97
 
77
98
  def action_permission
@@ -80,6 +101,8 @@ class JobInvocationsController < ApplicationController
80
101
  'create'
81
102
  when 'preview_hosts'
82
103
  'create'
104
+ when 'cancel'
105
+ 'cancel'
83
106
  else
84
107
  super
85
108
  end
@@ -87,7 +110,12 @@ class JobInvocationsController < ApplicationController
87
110
 
88
111
  def prepare_composer
89
112
  if params[:feature].present?
90
- JobInvocationComposer.for_feature(params[:feature], params[:host_ids], {})
113
+ inputs = params[:inputs].permit!.to_hash if params.include?(:inputs)
114
+ JobInvocationComposer.for_feature(
115
+ params[:feature],
116
+ params[:host_ids],
117
+ inputs
118
+ )
91
119
  else
92
120
  # triggering_params is a Hash
93
121
  # when a hash is merged into ActionController::Parameters,
@@ -116,13 +116,13 @@ module RemoteExecutionHelper
116
116
  :class => 'btn btn-default',
117
117
  :title => _('See the last task details'))
118
118
  end
119
- if authorized_for(:permission => :edit_foreman_tasks, :auth_object => task, :authorizer => task_authorizer)
120
- buttons << link_to(_('Cancel Job'), cancel_foreman_tasks_task_path(task),
119
+ if authorized_for(:permission => :cancel_job_invocations, :auth_object => job_invocation)
120
+ buttons << link_to(_('Cancel Job'), cancel_job_invocation_path(job_invocation),
121
121
  :class => 'btn btn-danger',
122
122
  :title => _('Try to cancel the job'),
123
123
  :disabled => !task.cancellable?,
124
124
  :method => :post)
125
- buttons << link_to(_('Abort Job'), abort_foreman_tasks_task_path(task),
125
+ buttons << link_to(_('Abort Job'), cancel_job_invocation_path(job_invocation, :force => true),
126
126
  :class => 'btn btn-danger',
127
127
  :title => _('Try to abort the job without waiting for the results from the remote hosts'),
128
128
  :disabled => !task.cancellable?,
@@ -132,14 +132,14 @@ module RemoteExecutionHelper
132
132
  end
133
133
  # rubocop:enable Metrics/AbcSize
134
134
 
135
- def template_invocation_task_buttons(task)
135
+ def template_invocation_task_buttons(task, invocation)
136
136
  buttons = []
137
137
  if authorized_for(:permission => :view_foreman_tasks, :auth_object => task)
138
138
  buttons << link_to(_('Task Details'), foreman_tasks_task_path(task),
139
139
  :class => 'btn btn-default',
140
140
  :title => _('See the task details'))
141
141
  end
142
- if authorized_for(:permission => :edit_foreman_tasks, :auth_object => task)
142
+ if authorized_for(:permission => :cancel_job_invocations, :auth_object => invocation)
143
143
  buttons << link_to(_('Cancel Job'), cancel_foreman_tasks_task_path(task),
144
144
  :class => 'btn btn-danger',
145
145
  :title => _('Try to cancel the job on a host'),
@@ -5,12 +5,12 @@ module Actions
5
5
  include ::Actions::Helpers::WithDelegatedAction
6
6
 
7
7
  middleware.do_not_use Dynflow::Middleware::Common::Transaction
8
+ middleware.use Actions::Middleware::HideSecrets
8
9
 
9
10
  def resource_locks
10
11
  :link
11
12
  end
12
13
 
13
- # rubocop:disable Metrics/AbcSize
14
14
  def plan(job_invocation, host, template_invocation, proxy_selector = ::RemoteExecutionProxySelector.new, options = {})
15
15
  action_subject(host, :job_category => job_invocation.job_category, :description => job_invocation.description)
16
16
 
@@ -25,32 +25,25 @@ module Actions
25
25
 
26
26
  raise _('Could not use any template used in the job invocation') if template_invocation.blank?
27
27
 
28
- provider = template_invocation.template.provider_type.to_s
29
- proxy = proxy_selector.determine_proxy(host, provider)
30
- if proxy == :not_available
31
- offline_proxies = proxy_selector.offline
32
- settings = { :count => offline_proxies.count, :proxy_names => offline_proxies.map(&:name).join(', ') }
33
- raise n_('The only applicable proxy %{proxy_names} is down',
34
- 'All %{count} applicable proxies are down. Tried %{proxy_names}',
35
- offline_proxies.count) % settings
36
- elsif proxy == :not_defined && !Setting['remote_execution_without_proxy']
37
- settings = { :global_proxy => 'remote_execution_global_proxy',
38
- :fallback_proxy => 'remote_execution_fallback_proxy',
39
- :no_proxy => 'remote_execution_no_proxy' }
40
-
41
- raise _('Could not use any proxy. Consider configuring %{global_proxy}, ' +
42
- '%{fallback_proxy} or %{no_proxy} in settings') % settings
43
- end
28
+ provider_type = template_invocation.template.provider_type.to_s
29
+ proxy = determine_proxy!(proxy_selector, provider_type, host)
44
30
 
45
31
  renderer = InputTemplateRenderer.new(template_invocation.template, host, template_invocation)
46
32
  script = renderer.render
47
33
  raise _('Failed rendering template: %s') % renderer.error_message unless script
48
34
 
49
35
  provider = template_invocation.template.provider
50
- hostname = provider.find_ip_or_hostname(host)
36
+
37
+ secrets = { :ssh_password => job_invocation.password || provider.ssh_password(host),
38
+ :key_passphrase => job_invocation.key_passphrase || provider.ssh_key_passphrase(host) }
39
+
40
+ additional_options = { :hostname => provider.find_ip_or_hostname(host),
41
+ :script => script,
42
+ :execution_timeout_interval => job_invocation.execution_timeout_interval,
43
+ :secrets => secrets }
51
44
  action_options = provider.proxy_command_options(template_invocation, host)
52
- .merge(:hostname => hostname, :script => script,
53
- :execution_timeout_interval => job_invocation.execution_timeout_interval)
45
+ .merge(additional_options)
46
+
54
47
  plan_delegated_action(proxy, ForemanRemoteExecutionCore::Actions::RunScript, action_options)
55
48
  plan_self
56
49
  end
@@ -144,6 +137,25 @@ module Actions
144
137
 
145
138
  true
146
139
  end
140
+
141
+ def determine_proxy!(proxy_selector, provider, host)
142
+ proxy = proxy_selector.determine_proxy(host, provider)
143
+ if proxy == :not_available
144
+ offline_proxies = proxy_selector.offline
145
+ settings = { :count => offline_proxies.count, :proxy_names => offline_proxies.map(&:name).join(', ') }
146
+ raise n_('The only applicable proxy %{proxy_names} is down',
147
+ 'All %{count} applicable proxies are down. Tried %{proxy_names}',
148
+ offline_proxies.count) % settings
149
+ elsif proxy == :not_defined && !Setting['remote_execution_without_proxy']
150
+ settings = { :global_proxy => 'remote_execution_global_proxy',
151
+ :fallback_proxy => 'remote_execution_fallback_proxy',
152
+ :no_proxy => 'remote_execution_no_proxy' }
153
+
154
+ raise _('Could not use any proxy. Consider configuring %{global_proxy}, ' +
155
+ '%{fallback_proxy} or %{no_proxy} in settings') % settings
156
+ end
157
+ proxy
158
+ end
147
159
  end
148
160
  end
149
161
  end
@@ -26,7 +26,6 @@ module Actions
26
26
  end
27
27
 
28
28
  def create_sub_plans
29
- job_invocation = JobInvocation.find(input[:job_invocation_id])
30
29
  proxy_selector = RemoteExecutionProxySelector.new
31
30
 
32
31
  current_batch.map do |host|
@@ -38,6 +37,18 @@ module Actions
38
37
  end
39
38
  end
40
39
 
40
+ def finalize
41
+ job_invocation.password = job_invocation.key_passphrase = nil
42
+ job_invocation.save!
43
+
44
+ # creating the success notification should be the very last thing this tasks do
45
+ job_invocation.build_notification.deliver!
46
+ end
47
+
48
+ def job_invocation
49
+ @job_invocation ||= JobInvocation.find(input[:job_invocation_id])
50
+ end
51
+
41
52
  def batch(from, size)
42
53
  hosts.offset(from).limit(size)
43
54
  end
@@ -47,7 +58,7 @@ module Actions
47
58
  end
48
59
 
49
60
  def hosts
50
- JobInvocation.find(input[:job_invocation_id]).targeting.hosts.order(:name, :id)
61
+ job_invocation.targeting.hosts.order(:name, :id)
51
62
  end
52
63
 
53
64
  def set_up_concurrency_control(invocation)
@@ -66,12 +77,6 @@ module Actions
66
77
  super unless event == Dynflow::Action::Skip
67
78
  end
68
79
 
69
- def finalize
70
- # creating the success notification should be the very last thing this tasks do
71
- job_invocation = JobInvocation.find(input[:job_invocation_id])
72
- job_invocation.build_notification.deliver!
73
- end
74
-
75
80
  def humanized_input
76
81
  input.fetch(:job_invocation, {}).fetch(:description, '')
77
82
  end
@@ -1,5 +1,5 @@
1
1
  class ForeignInputSet < ApplicationRecord
2
- include ForemanRemoteExecution::Exportable
2
+ include ::Exportable
3
3
 
4
4
  class CircularDependencyError < Foreman::Exception
5
5
  end
@@ -1,5 +1,7 @@
1
1
  class JobInvocation < ApplicationRecord
2
2
  include Authorizable
3
+ include Encryptable
4
+
3
5
  audited :except => [ :task_id, :targeting_id, :task_group_id, :triggering_id ]
4
6
 
5
7
  include ForemanRemoteExecution::ErrorsFlattener
@@ -47,6 +49,8 @@ class JobInvocation < ApplicationRecord
47
49
  belongs_to :triggering, :class_name => 'ForemanTasks::Triggering'
48
50
  has_one :recurring_logic, :through => :triggering, :class_name => 'ForemanTasks::RecurringLogic'
49
51
 
52
+ belongs_to :remote_execution_feature
53
+
50
54
  scope :with_task, -> { references(:task) }
51
55
 
52
56
  scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id'
@@ -64,6 +68,8 @@ class JobInvocation < ApplicationRecord
64
68
 
65
69
  delegate :start_at, :to => :task, :allow_nil => true
66
70
 
71
+ encrypts :password, :key_passphrase
72
+
67
73
  def self.search_by_status(key, operator, value)
68
74
  conditions = HostStatus::ExecutionStatus::ExecutionTaskStatusMapper.sql_conditions_for(value)
69
75
  conditions[0] = "NOT (#{conditions[0]})" if operator == '<>'
@@ -83,7 +89,16 @@ class JobInvocation < ApplicationRecord
83
89
  end
84
90
 
85
91
  def build_notification
86
- UINotifications::RemoteExecutionJobs::BaseJobFinish.new(self)
92
+ klass = nil
93
+ if self.remote_execution_feature && self.remote_execution_feature.notification_builder.present?
94
+ begin
95
+ klass = remote_execution_feature.notification_builder.constantize
96
+ rescue NameError => e
97
+ logger.exception "REX feature defines unknown notification builder class", e
98
+ end
99
+ end
100
+ klass ||= UINotifications::RemoteExecutionJobs::BaseJobFinish
101
+ klass.new(self)
87
102
  end
88
103
 
89
104
  def status
@@ -120,6 +135,8 @@ class JobInvocation < ApplicationRecord
120
135
  invocation.description_format = self.description_format
121
136
  invocation.description = self.description
122
137
  invocation.pattern_template_invocations = self.pattern_template_invocations.map(&:deep_clone)
138
+ invocation.password = self.password
139
+ invocation.key_passphrase = self.key_passphrase
123
140
  end
124
141
  end
125
142
 
@@ -208,6 +225,11 @@ class JobInvocation < ApplicationRecord
208
225
  end
209
226
  end
210
227
 
228
+ def cancel(force = false)
229
+ method = force ? :abort : :cancel
230
+ task.send(method)
231
+ end
232
+
211
233
  private
212
234
 
213
235
  def failed_template_invocations
@@ -11,7 +11,10 @@ class JobInvocationComposer
11
11
  :targeting => ui_params.fetch(:targeting, {}).merge(:user_id => User.current.id),
12
12
  :triggering => triggering,
13
13
  :host_ids => ui_params[:host_ids],
14
+ :remote_execution_feature_id => ui_params[:remote_execution_feature_id],
14
15
  :description_format => job_invocation_base[:description_format],
16
+ :password => blank_to_nil(job_invocation_base[:password]),
17
+ :key_passphrase => blank_to_nil(job_invocation_base[:key_passphrase]),
15
18
  :concurrency_control => concurrency_control_params,
16
19
  :execution_timeout_interval => execution_timeout_interval,
17
20
  :template_invocations => template_invocations_params }.with_indifferent_access
@@ -32,6 +35,10 @@ class JobInvocationComposer
32
35
  end.first
33
36
  end
34
37
 
38
+ def blank_to_nil(thing)
39
+ thing.blank? ? nil : thing
40
+ end
41
+
35
42
  # TODO: Fix this comment
36
43
  # parses params to get job templates in form of id => attributes for selected job templates, e.g.
37
44
  # {
@@ -88,6 +95,7 @@ class JobInvocationComposer
88
95
  :targeting => targeting_params,
89
96
  :triggering => triggering_params,
90
97
  :description_format => api_params[:description_format],
98
+ :remote_execution_feature_id => api_params[:remote_execution_feature_id],
91
99
  :concurrency_control => concurrency_control_params,
92
100
  :execution_timeout_interval => api_params[:execution_timeout_interval] || template.execution_timeout_interval,
93
101
  :template_invocations => template_invocations_params }.with_indifferent_access
@@ -172,6 +180,7 @@ class JobInvocationComposer
172
180
  :description_format => job_invocation.description_format,
173
181
  :concurrency_control => concurrency_control_params,
174
182
  :execution_timeout_interval => job_invocation.execution_timeout_interval,
183
+ :remote_execution_feature_id => job_invocation.remote_execution_feature_id,
175
184
  :template_invocations => template_invocations_params }.with_indifferent_access
176
185
  end
177
186
 
@@ -185,10 +194,12 @@ class JobInvocationComposer
185
194
  end
186
195
 
187
196
  def targeting_params
197
+ base = { :user_id => User.current.id }
188
198
  if @host_ids
189
- { :search_query => Targeting.build_query_from_hosts(@host_ids), :targeting_type => job_invocation.targeting.targeting_type }
199
+ search_query = @host_ids.empty? ? 'name ^ ()' : Targeting.build_query_from_hosts(@host_ids)
200
+ base.merge(:search_query => search_query, :targeting_type => job_invocation.targeting.targeting_type)
190
201
  else
191
- job_invocation.targeting.attributes.slice('search_query', 'bookmark_id', 'user_id', 'targeting_type')
202
+ base.merge job_invocation.targeting.attributes.slice('search_query', 'bookmark_id', 'targeting_type')
192
203
  end
193
204
  end
194
205
 
@@ -215,6 +226,10 @@ class JobInvocationComposer
215
226
  @host_bookmark = hosts
216
227
  elsif hosts.is_a? Host::Base
217
228
  @host_objects = [hosts]
229
+ elsif hosts.is_a? Array
230
+ @host_objects = hosts.map do |id|
231
+ Host::Managed.authorized.friendly.find(id)
232
+ end
218
233
  elsif hosts.is_a? String
219
234
  @host_scoped_search = hosts
220
235
  else
@@ -227,6 +242,7 @@ class JobInvocationComposer
227
242
  :targeting => targeting_params,
228
243
  :triggering => {},
229
244
  :concurrency_control => {},
245
+ :remote_execution_feature_id => @feature.id,
230
246
  :template_invocations => template_invocations_params }.with_indifferent_access
231
247
  end
232
248
 
@@ -248,6 +264,7 @@ class JobInvocationComposer
248
264
  end
249
265
 
250
266
  def input_values_params
267
+ return {} if @provided_inputs.blank?
251
268
  @provided_inputs.map do |key, value|
252
269
  input = job_template.template_inputs_with_foreign.find { |i| i.name == key.to_s }
253
270
  unless input
@@ -275,7 +292,7 @@ class JobInvocationComposer
275
292
  end
276
293
 
277
294
  attr_accessor :params, :job_invocation, :host_ids, :search_query
278
- delegate :job_category, :pattern_template_invocations, :template_invocations, :targeting, :triggering, :to => :job_invocation
295
+ delegate :job_category, :remote_execution_feature_id, :pattern_template_invocations, :template_invocations, :targeting, :triggering, :to => :job_invocation
279
296
 
280
297
  def initialize(params, set_defaults = false)
281
298
  @params = params
@@ -304,9 +321,11 @@ class JobInvocationComposer
304
321
  self.new(ParamsForFeature.new(feature_label, hosts, provided_inputs).params)
305
322
  end
306
323
 
324
+ # rubocop:disable Metrics/AbcSize
307
325
  def compose
308
326
  job_invocation.job_category = validate_job_category(params[:job_category])
309
327
  job_invocation.job_category ||= available_job_categories.first if @set_defaults
328
+ job_invocation.remote_execution_feature_id = params[:remote_execution_feature_id]
310
329
  job_invocation.targeting = build_targeting
311
330
  job_invocation.triggering = build_triggering
312
331
  job_invocation.pattern_template_invocations = build_template_invocations
@@ -314,9 +333,12 @@ class JobInvocationComposer
314
333
  job_invocation.time_span = params[:concurrency_control][:time_span].to_i if params[:concurrency_control][:time_span].present?
315
334
  job_invocation.concurrency_level = params[:concurrency_control][:level].to_i if params[:concurrency_control][:level].present?
316
335
  job_invocation.execution_timeout_interval = params[:execution_timeout_interval]
336
+ job_invocation.password = params[:password]
337
+ job_invocation.key_passphrase = params[:key_passphrase]
317
338
 
318
339
  self
319
340
  end
341
+ # rubocop:enable Metrics/AbcSize
320
342
 
321
343
  def trigger(raise_on_error = false)
322
344
  generate_description