foreman_remote_execution 4.4.0 → 4.5.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.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/job_invocations_controller.rb +13 -24
- data/app/controllers/job_templates_controller.rb +4 -4
- data/app/controllers/ui_job_wizard_controller.rb +12 -0
- data/app/helpers/job_invocations_helper.rb +2 -2
- data/app/helpers/remote_execution_helper.rb +8 -8
- data/app/lib/actions/remote_execution/run_host_job.rb +31 -5
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +5 -5
- data/app/models/host_status/execution_status.rb +5 -5
- data/app/models/job_invocation.rb +23 -7
- data/app/models/job_invocation_composer.rb +59 -17
- data/app/models/ssh_execution_provider.rb +4 -4
- data/app/overrides/execution_interface.rb +8 -8
- data/app/overrides/subnet_proxies.rb +6 -6
- data/config/routes.rb +1 -0
- data/db/migrate/20180110104432_rename_template_invocation_permission.rb +1 -1
- data/db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb +4 -4
- data/extra/cockpit/foreman-cockpit-session +6 -6
- data/lib/foreman_remote_execution/engine.rb +11 -6
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/package.json +2 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +14 -1
- data/test/unit/job_invocation_composer_test.rb +45 -1
- data/webpack/JobWizard/JobWizard.js +59 -18
- data/webpack/JobWizard/JobWizard.scss +3 -1
- data/webpack/JobWizard/JobWizardConstants.js +1 -0
- data/webpack/JobWizard/JobWizardSelectors.js +18 -1
- data/webpack/JobWizard/__tests__/JobWizard.test.js +4 -11
- data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -51
- data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +43 -0
- data/webpack/JobWizard/__tests__/fixtures.js +26 -0
- data/webpack/JobWizard/__tests__/integration.test.js +156 -0
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +93 -0
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +181 -0
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +25 -0
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +249 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +34 -2
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +10 -3
- data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +50 -1
- data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +9 -1
- data/webpack/JobWizard/steps/form/FormHelpers.js +19 -0
- data/webpack/JobWizard/steps/form/GroupedSelectField.js +3 -0
- data/webpack/JobWizard/steps/form/SelectField.js +10 -1
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +1 -0
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +1 -0
- data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +21 -2
- data/webpack/global_index.js +5 -3
- data/webpack/index.js +3 -0
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +1 -5
- data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +8 -3
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +7 -2
- data/webpack/react_app/extend/{fills.js → fillRecentJobsCard.js} +7 -6
- data/webpack/react_app/extend/fillregistrationAdvanced.js +11 -0
- data/webpack/react_app/extend/reducers.js +2 -1
- metadata +12 -4
- data/webpack/fills_index.js +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 113fb04d80f92308f55da87e148b2c30de510b5a97dda5c400bdac598d3a407f
|
4
|
+
data.tar.gz: 42a44014bbb85af6ccef059d4d716a9e840865be30735cea61fb65cd94a63d2f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6321f8795de48743746748a71b3a64d4c75de23f55bfe0e79bfd660a54ecd2b5522f10554fa96c16d11350a6800457b47ae0a82fec7f905142f9d062103b3ce6
|
7
|
+
data.tar.gz: 41962649438f42c27845b6e7b6694cff343c4f44e8b7771534a25127458ed2b9a15b404e319e00958f36f81a0d139733820873dd5a2771cb5efeaaebbbfa7223
|
@@ -80,18 +80,15 @@ module Api
|
|
80
80
|
api :POST, '/job_invocations/', N_('Create a job invocation')
|
81
81
|
param_group :job_invocation, :as => :create
|
82
82
|
def create
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
validate_template
|
87
|
-
composer = JobInvocationComposer.from_api_params(
|
88
|
-
job_invocation_params
|
89
|
-
)
|
90
|
-
end
|
83
|
+
composer = JobInvocationComposer.from_api_params(
|
84
|
+
job_invocation_params
|
85
|
+
)
|
91
86
|
composer.trigger!
|
92
87
|
@job_invocation = composer.job_invocation
|
93
88
|
@hosts = @job_invocation.targeting.hosts
|
94
89
|
process_response @job_invocation
|
90
|
+
rescue JobInvocationComposer::JobTemplateNotFound, JobInvocationComposer::FeatureNotFound => e
|
91
|
+
not_found(error: { message: e.message })
|
95
92
|
end
|
96
93
|
|
97
94
|
api :GET, '/job_invocations/:id/hosts/:host_id', N_('Get output for a host')
|
@@ -128,7 +125,7 @@ module Api
|
|
128
125
|
render :json => { :cancelled => result, :id => @job_invocation.id }
|
129
126
|
else
|
130
127
|
render :json => { :message => _('The job could not be cancelled.') },
|
131
|
-
|
128
|
+
:status => :unprocessable_entity
|
132
129
|
end
|
133
130
|
end
|
134
131
|
|
@@ -143,7 +140,7 @@ module Api
|
|
143
140
|
process_response @job_invocation
|
144
141
|
else
|
145
142
|
render :json => { :error => _('Could not rerun job %{id} because its template could not be found') % { :id => composer.reruns } },
|
146
|
-
|
143
|
+
:status => :not_found
|
147
144
|
end
|
148
145
|
end
|
149
146
|
|
@@ -190,19 +187,19 @@ module Api
|
|
190
187
|
not_found({ :error => { :message => (_("Host with id '%{id}' was not found") % { :id => params['host_id'] }) } })
|
191
188
|
end
|
192
189
|
|
193
|
-
def validate_template
|
194
|
-
JobTemplate.authorized(:view_job_templates).find(job_invocation_params['job_template_id'])
|
195
|
-
rescue ActiveRecord::RecordNotFound
|
196
|
-
not_found({ :error => { :message => (_("Template with id '%{id}' was not found") % { :id => job_invocation_params['job_template_id'] }) } })
|
197
|
-
end
|
198
|
-
|
199
190
|
def job_invocation_params
|
200
191
|
return @job_invocation_params if @job_invocation_params.present?
|
201
192
|
|
202
193
|
job_invocation_params = params.fetch(:job_invocation, {}).dup
|
194
|
+
|
195
|
+
if job_invocation_params[:feature].present? && job_invocation_params[:job_template_id].present?
|
196
|
+
raise _("Only one of feature or job_template_id can be specified")
|
197
|
+
end
|
198
|
+
|
203
199
|
if job_invocation_params.key?(:ssh)
|
204
200
|
job_invocation_params.merge!(job_invocation_params.delete(:ssh).permit(:effective_user))
|
205
201
|
end
|
202
|
+
|
206
203
|
job_invocation_params[:inputs] ||= {}
|
207
204
|
job_invocation_params[:inputs].permit!
|
208
205
|
permit_provider_inputs job_invocation_params
|
@@ -214,14 +211,6 @@ module Api
|
|
214
211
|
providers.each { |provider| invocation_params[provider.provider_input_namespace]&.permit! }
|
215
212
|
end
|
216
213
|
|
217
|
-
def composer_for_feature
|
218
|
-
JobInvocationComposer.for_feature(
|
219
|
-
job_invocation_params[:feature],
|
220
|
-
job_invocation_params[:host_ids],
|
221
|
-
job_invocation_params[:inputs].to_hash
|
222
|
-
)
|
223
|
-
end
|
224
|
-
|
225
214
|
def output_lines_since(task, time)
|
226
215
|
since = time.to_f if time.present?
|
227
216
|
line_sets = task.main_action.live_output
|
@@ -24,10 +24,10 @@ class JobTemplatesController < ::TemplatesController
|
|
24
24
|
render :plain => output
|
25
25
|
else
|
26
26
|
render status: :not_acceptable,
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
plain: _(
|
28
|
+
'Problem with previewing the template: %{error}. Note that you must save template input changes before you try to preview it.' %
|
29
|
+
{:error => renderer.error_message}
|
30
|
+
)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
@@ -8,6 +8,18 @@ class UiJobWizardController < ::Api::V2::BaseController
|
|
8
8
|
render :json => {:job_categories =>job_categories}
|
9
9
|
end
|
10
10
|
|
11
|
+
def template
|
12
|
+
job_template = JobTemplate.authorized.find(params[:id])
|
13
|
+
render :json => {
|
14
|
+
:job_template => job_template,
|
15
|
+
:effective_user => job_template.effective_user,
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def resource_name(nested_resource = nil)
|
20
|
+
nested_resource || 'job_template'
|
21
|
+
end
|
22
|
+
|
11
23
|
def resource_class
|
12
24
|
JobTemplate
|
13
25
|
end
|
@@ -34,8 +34,8 @@ module JobInvocationsHelper
|
|
34
34
|
hosts.map do |host|
|
35
35
|
collapsed_preview(host) +
|
36
36
|
render(:partial => 'job_invocations/user_input',
|
37
|
-
|
38
|
-
|
37
|
+
:locals => { :template_invocation => template_invocation,
|
38
|
+
:target => host })
|
39
39
|
end.reduce(:+)
|
40
40
|
end
|
41
41
|
|
@@ -27,19 +27,19 @@ module RemoteExecutionHelper
|
|
27
27
|
|
28
28
|
if authorized_for(hash_for_host_path(host).merge(auth_object: host, permission: :view_hosts, authorizer: job_hosts_authorizer))
|
29
29
|
links << { title: _('Host detail'),
|
30
|
-
|
30
|
+
action: { href: host_path(host), 'data-method': 'get', id: "#{host.name}-actions-detail" } }
|
31
31
|
end
|
32
32
|
|
33
33
|
if authorized_for(hash_for_rerun_job_invocation_path(id: job_invocation, host_ids: [ host.id ], authorizer: job_hosts_authorizer))
|
34
34
|
links << { title: (_('Rerun on %s') % host.name),
|
35
|
-
|
36
|
-
|
35
|
+
action: { href: rerun_job_invocation_path(job_invocation, host_ids: [ host.id ]),
|
36
|
+
'data-method': 'get', id: "#{host.name}-actions-rerun" } }
|
37
37
|
end
|
38
38
|
|
39
39
|
if host_task.present? && authorized_for(hash_for_foreman_tasks_task_path(host_task).merge(auth_object: host_task, permission: :view_foreman_tasks))
|
40
40
|
links << { title: _('Host task'),
|
41
|
-
|
42
|
-
|
41
|
+
action: { href: foreman_tasks_task_path(host_task),
|
42
|
+
'data-method': 'get', id: "#{host.name}-actions-task" } }
|
43
43
|
end
|
44
44
|
|
45
45
|
links
|
@@ -159,11 +159,11 @@ module RemoteExecutionHelper
|
|
159
159
|
content_tag :pre, preview
|
160
160
|
elsif target.nil?
|
161
161
|
alert :text => _('Could not render the preview because no host matches the search query.'),
|
162
|
-
|
163
|
-
|
162
|
+
:class => 'alert alert-block alert-warning base',
|
163
|
+
:close => false
|
164
164
|
else
|
165
165
|
alert :class => 'alert-block alert-danger base in fade has-error',
|
166
|
-
|
166
|
+
:text => renderer.error_message.html_safe # rubocop:disable Rails/OutputSafety
|
167
167
|
end
|
168
168
|
end
|
169
169
|
|
@@ -5,6 +5,8 @@ module Actions
|
|
5
5
|
include ::Actions::Helpers::WithDelegatedAction
|
6
6
|
include ::Actions::ObservableAction
|
7
7
|
|
8
|
+
execution_plan_hooks.use :emit_feature_event, :on => :success
|
9
|
+
|
8
10
|
middleware.do_not_use Dynflow::Middleware::Common::Transaction
|
9
11
|
middleware.use Actions::Middleware::HideSecrets
|
10
12
|
|
@@ -17,7 +19,12 @@ module Actions
|
|
17
19
|
end
|
18
20
|
|
19
21
|
def plan(job_invocation, host, template_invocation, proxy_selector = ::RemoteExecutionProxySelector.new, options = {})
|
20
|
-
|
22
|
+
features = template_invocation.template.remote_execution_features.pluck(:label).uniq
|
23
|
+
action_subject(host,
|
24
|
+
:job_category => job_invocation.job_category,
|
25
|
+
:description => job_invocation.description,
|
26
|
+
:job_invocation_id => job_invocation.id,
|
27
|
+
:job_features => features)
|
21
28
|
|
22
29
|
template_invocation.host_id = host.id
|
23
30
|
template_invocation.run_host_job_task_id = task.id
|
@@ -58,6 +65,22 @@ module Actions
|
|
58
65
|
check_exit_status
|
59
66
|
end
|
60
67
|
|
68
|
+
def self.feature_job_event_name(label, suffix = :success)
|
69
|
+
::Foreman::Observable.event_name_for("#{::Actions::RemoteExecution::RunHostJob.event_name_base}_#{label}_#{::Actions::RemoteExecution::RunHostJob.event_name_suffix(suffix)}")
|
70
|
+
end
|
71
|
+
|
72
|
+
def emit_feature_event(execution_plan, hook = :success)
|
73
|
+
return unless root_action?
|
74
|
+
|
75
|
+
payload = event_payload(execution_plan)
|
76
|
+
if input["job_features"]&.any?
|
77
|
+
input['job_features'].each do |feature|
|
78
|
+
name = "#{self.class.event_name_base}_#{feature}_#{self.class.event_name_suffix(hook)}"
|
79
|
+
trigger_hook name, payload: payload
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
61
84
|
def secrets(host, job_invocation, provider)
|
62
85
|
job_secrets = { :ssh_password => job_invocation.password,
|
63
86
|
:key_passphrase => job_invocation.key_passphrase,
|
@@ -78,7 +101,7 @@ module Actions
|
|
78
101
|
|
79
102
|
def humanized_input
|
80
103
|
N_('%{description} on %{host}') % { :host => input[:host].try(:[], :name),
|
81
|
-
|
104
|
+
:description => input[:description].try(:capitalize) || input[:job_category] }
|
82
105
|
end
|
83
106
|
|
84
107
|
def humanized_name
|
@@ -186,10 +209,13 @@ module Actions
|
|
186
209
|
'All %{count} applicable proxies are down. Tried %{proxy_names}',
|
187
210
|
offline_proxies.count) % settings
|
188
211
|
elsif proxy == :not_defined
|
189
|
-
settings = {
|
190
|
-
|
212
|
+
settings = {
|
213
|
+
global_proxy: 'remote_execution_global_proxy',
|
214
|
+
fallback_proxy: 'remote_execution_fallback_proxy',
|
215
|
+
provider: provider,
|
216
|
+
}
|
191
217
|
|
192
|
-
raise _('Could not use any proxy. Consider configuring %{global_proxy}, ' +
|
218
|
+
raise _('Could not use any proxy for the %{provider} job. Consider configuring %{global_proxy}, ' +
|
193
219
|
'%{fallback_proxy} in settings') % settings
|
194
220
|
end
|
195
221
|
proxy
|
@@ -8,15 +8,15 @@ module ForemanRemoteExecution
|
|
8
8
|
has_many :run_host_job_tasks, :through => :template_invocations
|
9
9
|
|
10
10
|
scoped_search :relation => :run_host_job_tasks, :on => :result, :rename => 'job_invocation.result',
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
:ext_method => :search_by_job_invocation,
|
12
|
+
:only_explicit => true,
|
13
|
+
:complete_value => TemplateInvocation::TaskResultMap::REVERSE_MAP
|
14
14
|
|
15
15
|
scoped_search :relation => :template_invocations, :on => :job_invocation_id,
|
16
|
-
|
16
|
+
:rename => 'job_invocation.id', :only_explicit => true, :ext_method => :search_by_job_invocation
|
17
17
|
|
18
18
|
scoped_search :relation => :execution_status_object, :on => :status, :rename => :execution_status,
|
19
|
-
|
19
|
+
:complete_value => { :ok => HostStatus::ExecutionStatus::OK, :error => HostStatus::ExecutionStatus::ERROR }
|
20
20
|
|
21
21
|
def search_by_job_invocation(key, operator, value)
|
22
22
|
if key == 'job_invocation.result'
|
@@ -64,15 +64,15 @@ class HostStatus::ExecutionStatus < HostStatus::Status
|
|
64
64
|
|
65
65
|
case status
|
66
66
|
when OK
|
67
|
-
[ "state = 'stopped' AND result = 'success'" ]
|
67
|
+
[ "foreman_tasks_tasks.state = 'stopped' AND result = 'success'" ]
|
68
68
|
when CANCELLED
|
69
|
-
[ "state = 'stopped' AND result = 'cancelled'" ]
|
69
|
+
[ "foreman_tasks_tasks.state = 'stopped' AND result = 'cancelled'" ]
|
70
70
|
when ERROR
|
71
|
-
[ "state = 'stopped' AND (result = 'error' OR result = 'warning')" ]
|
71
|
+
[ "foreman_tasks_tasks.state = 'stopped' AND (result = 'error' OR result = 'warning')" ]
|
72
72
|
when QUEUED
|
73
|
-
[ "state = 'scheduled' OR state IS NULL" ]
|
73
|
+
[ "foreman_tasks_tasks.state = 'scheduled' OR foreman_tasks_tasks.state IS NULL" ]
|
74
74
|
when RUNNING
|
75
|
-
[ "state <> 'stopped'" ]
|
75
|
+
[ "foreman_tasks_tasks.state <> 'stopped'" ]
|
76
76
|
else
|
77
77
|
[ '1 = 0' ]
|
78
78
|
end
|
@@ -15,8 +15,9 @@ class JobInvocation < ApplicationRecord
|
|
15
15
|
|
16
16
|
belongs_to :targeting, :dependent => :destroy
|
17
17
|
has_many :all_template_invocations, :inverse_of => :job_invocation, :dependent => :destroy, :class_name => 'TemplateInvocation'
|
18
|
-
has_many :template_invocations, -> { where('host_id IS NOT NULL') }, :inverse_of => :job_invocation
|
19
|
-
has_many :pattern_template_invocations, -> { where('host_id IS NULL') }, :inverse_of => :job_invocation, :class_name => 'TemplateInvocation'
|
18
|
+
has_many :template_invocations, -> { where('template_invocations.host_id IS NOT NULL') }, :inverse_of => :job_invocation
|
19
|
+
has_many :pattern_template_invocations, -> { where('template_invocations.host_id IS NULL') }, :inverse_of => :job_invocation, :class_name => 'TemplateInvocation'
|
20
|
+
has_many :pattern_templates, :through => :pattern_template_invocations, :source => :template
|
20
21
|
|
21
22
|
validates :targeting, :presence => true
|
22
23
|
validates :job_category, :presence => true
|
@@ -44,26 +45,33 @@ class JobInvocation < ApplicationRecord
|
|
44
45
|
:source => 'run_host_job_task'
|
45
46
|
has_one :user, through: :task
|
46
47
|
scoped_search relation: :user, on: :login, rename: 'user', complete_value: true,
|
47
|
-
|
48
|
-
|
48
|
+
value_translation: ->(value) { value == 'current_user' ? User.current.login : value },
|
49
|
+
special_values: [:current_user], aliases: ['owner'], :only_explicit => true
|
49
50
|
scoped_search :relation => :task, :on => :started_at, :rename => 'started_at', :complete_value => true
|
50
51
|
scoped_search :relation => :task, :on => :start_at, :rename => 'start_at', :complete_value => true
|
51
52
|
scoped_search :relation => :task, :on => :ended_at, :rename => 'ended_at', :complete_value => true
|
52
53
|
scoped_search :relation => :task, :on => :state, :rename => 'status', :ext_method => :search_by_status,
|
53
|
-
|
54
|
+
:only_explicit => true, :complete_value => Hash[HostStatus::ExecutionStatus::STATUS_NAMES.values.map { |v| [v, v] }]
|
54
55
|
|
55
56
|
belongs_to :triggering, :class_name => 'ForemanTasks::Triggering'
|
56
57
|
has_one :recurring_logic, :through => :triggering, :class_name => 'ForemanTasks::RecurringLogic'
|
57
58
|
|
58
59
|
belongs_to :remote_execution_feature
|
59
60
|
|
61
|
+
has_many :targeted_hosts, :through => :targeting, :source => :hosts
|
62
|
+
scoped_search :on => 'targeted_host_id', :rename => 'targeted_host_id', :operators => ['= '],
|
63
|
+
:complete_value => false, :only_explicit => true, :ext_method => :search_by_targeted_host
|
64
|
+
|
65
|
+
scoped_search :on => 'pattern_template_name', :rename => 'pattern_template_name', :operators => ['= '],
|
66
|
+
:complete_value => false, :only_explicit => true, :ext_method => :search_by_pattern_template
|
67
|
+
|
60
68
|
scope :with_task, -> { references(:task) }
|
61
69
|
|
62
70
|
scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id'
|
63
71
|
|
64
72
|
scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring',
|
65
|
-
|
66
|
-
|
73
|
+
:ext_method => :search_by_recurring_logic, :only_explicit => true,
|
74
|
+
:complete_value => { :true => true, :false => false }
|
67
75
|
|
68
76
|
default_scope -> { order('job_invocations.id DESC') }
|
69
77
|
|
@@ -80,6 +88,14 @@ class JobInvocation < ApplicationRecord
|
|
80
88
|
allow :sub_task_for_host, :template_invocations_hosts
|
81
89
|
end
|
82
90
|
|
91
|
+
def self.search_by_targeted_host(key, operator, value)
|
92
|
+
{ :conditions => sanitize_sql_for_conditions(["hosts.id = ?", value]), :joins => :targeted_hosts }
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.search_by_pattern_template(key, operator, value)
|
96
|
+
{ :conditions => sanitize_sql_for_conditions(["templates.name = ?", value]), :joins => :pattern_templates }
|
97
|
+
end
|
98
|
+
|
83
99
|
def self.search_by_status(key, operator, value)
|
84
100
|
conditions = HostStatus::ExecutionStatus::ExecutionTaskStatusMapper.sql_conditions_for(value)
|
85
101
|
conditions[0] = "NOT (#{conditions[0]})" if operator == '<>'
|
@@ -1,4 +1,7 @@
|
|
1
1
|
class JobInvocationComposer
|
2
|
+
class JobTemplateNotFound < StandardError; end
|
3
|
+
|
4
|
+
class FeatureNotFound < StandardError; end
|
2
5
|
|
3
6
|
class UiParams
|
4
7
|
attr_reader :ui_params
|
@@ -100,6 +103,17 @@ class JobInvocationComposer
|
|
100
103
|
|
101
104
|
def initialize(api_params)
|
102
105
|
@api_params = api_params
|
106
|
+
|
107
|
+
if api_params[:feature]
|
108
|
+
# set a default targeting type for backward compatibility
|
109
|
+
# when `for_feature` was used by the API it automatically set a default
|
110
|
+
api_params[:targeting_type] = Targeting::STATIC_TYPE
|
111
|
+
end
|
112
|
+
|
113
|
+
if api_params[:search_query].blank? && api_params[:host_ids].present?
|
114
|
+
translator = HostIdsTranslator.new(api_params[:host_ids])
|
115
|
+
api_params[:search_query] = translator.scoped_search
|
116
|
+
end
|
103
117
|
end
|
104
118
|
|
105
119
|
def params
|
@@ -107,12 +121,16 @@ class JobInvocationComposer
|
|
107
121
|
:targeting => targeting_params,
|
108
122
|
:triggering => triggering_params,
|
109
123
|
:description_format => api_params[:description_format],
|
110
|
-
:remote_execution_feature_id =>
|
124
|
+
:remote_execution_feature_id => remote_execution_feature_id,
|
111
125
|
:concurrency_control => concurrency_control_params,
|
112
126
|
:execution_timeout_interval => api_params[:execution_timeout_interval] || template.execution_timeout_interval,
|
113
127
|
:template_invocations => template_invocations_params }.with_indifferent_access
|
114
128
|
end
|
115
129
|
|
130
|
+
def remote_execution_feature_id
|
131
|
+
feature&.id || api_params[:remote_execution_feature_id]
|
132
|
+
end
|
133
|
+
|
116
134
|
def targeting_params
|
117
135
|
raise ::Foreman::Exception, _('Cannot specify both bookmark_id and search_query') if api_params[:bookmark_id] && api_params[:search_query]
|
118
136
|
|
@@ -170,8 +188,20 @@ class JobInvocationComposer
|
|
170
188
|
inputs.select { |key, value| provider_input_names.include? key }.map { |key, value| { :name => key, :value => value } }
|
171
189
|
end
|
172
190
|
|
191
|
+
def feature
|
192
|
+
@feature ||= RemoteExecutionFeature.feature(api_params[:feature]) if api_params[:feature]
|
193
|
+
rescue => e
|
194
|
+
raise(FeatureNotFound, e.message)
|
195
|
+
end
|
196
|
+
|
197
|
+
def job_template_id
|
198
|
+
feature&.job_template_id || api_params[:job_template_id]
|
199
|
+
end
|
200
|
+
|
173
201
|
def template
|
174
|
-
@template ||= JobTemplate.authorized(:view_job_templates).find(
|
202
|
+
@template ||= JobTemplate.authorized(:view_job_templates).find(job_template_id)
|
203
|
+
rescue ActiveRecord::RecordNotFound
|
204
|
+
raise(JobTemplateNotFound, _("Template with id '%{id}' was not found") % { id: job_template_id })
|
175
205
|
end
|
176
206
|
|
177
207
|
private
|
@@ -239,25 +269,38 @@ class JobInvocationComposer
|
|
239
269
|
end
|
240
270
|
end
|
241
271
|
|
272
|
+
class HostIdsTranslator
|
273
|
+
attr_reader :bookmark, :hosts, :scoped_search, :host_ids
|
274
|
+
|
275
|
+
def initialize(input)
|
276
|
+
case input
|
277
|
+
when Bookmark
|
278
|
+
@bookmark = input
|
279
|
+
when Host::Base
|
280
|
+
@hosts = [input]
|
281
|
+
when Array
|
282
|
+
@hosts = input.map do |id|
|
283
|
+
Host::Managed.authorized.friendly.find(id)
|
284
|
+
end
|
285
|
+
when String
|
286
|
+
@scoped_search = input
|
287
|
+
else
|
288
|
+
@hosts = input
|
289
|
+
end
|
290
|
+
|
291
|
+
@scoped_search ||= Targeting.build_query_from_hosts(hosts.map(&:id)) if @hosts
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
242
295
|
class ParamsForFeature
|
243
296
|
attr_reader :feature_label, :feature, :provided_inputs
|
244
297
|
|
245
298
|
def initialize(feature_label, hosts, provided_inputs = {})
|
246
299
|
@feature = RemoteExecutionFeature.feature(feature_label)
|
247
300
|
@provided_inputs = provided_inputs
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
@host_objects = [hosts]
|
252
|
-
elsif hosts.is_a? Array
|
253
|
-
@host_objects = hosts.map do |id|
|
254
|
-
Host::Managed.authorized.friendly.find(id)
|
255
|
-
end
|
256
|
-
elsif hosts.is_a? String
|
257
|
-
@host_scoped_search = hosts
|
258
|
-
else
|
259
|
-
@host_objects = hosts
|
260
|
-
end
|
301
|
+
translator = HostIdsTranslator.new(hosts)
|
302
|
+
@host_bookmark = translator.bookmark
|
303
|
+
@host_scoped_search = translator.scoped_search
|
261
304
|
end
|
262
305
|
|
263
306
|
def params
|
@@ -275,7 +318,6 @@ class JobInvocationComposer
|
|
275
318
|
ret = {}
|
276
319
|
ret['targeting_type'] = Targeting::STATIC_TYPE
|
277
320
|
ret['search_query'] = @host_scoped_search if @host_scoped_search
|
278
|
-
ret['search_query'] = Targeting.build_query_from_hosts(@host_objects) if @host_objects
|
279
321
|
ret['bookmark_id'] = @host_bookmark.id if @host_bookmark
|
280
322
|
ret['user_id'] = User.current.id
|
281
323
|
ret
|
@@ -553,7 +595,7 @@ class JobInvocationComposer
|
|
553
595
|
|
554
596
|
params[:template_invocations].select { |t| valid_template_ids.include?(t[:template_id].to_i) }.map do |template_invocation_params|
|
555
597
|
template_invocation = job_invocation.pattern_template_invocations.build(:template_id => template_invocation_params[:template_id],
|
556
|
-
|
598
|
+
:effective_user => build_effective_user(template_invocation_params))
|
557
599
|
build_input_values_for(template_invocation, template_invocation_params)
|
558
600
|
template_invocation
|
559
601
|
end
|