foreman_remote_execution 1.4.5 → 1.4.6

Sign up to get free protection for your applications and to get access to all the features.
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