foreman_remote_execution 4.3.1 → 4.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +27 -22
  3. data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_commands_controller_extensions.rb +19 -0
  4. data/app/controllers/job_invocations_controller.rb +1 -1
  5. data/app/controllers/job_templates_controller.rb +4 -4
  6. data/app/controllers/ui_job_wizard_controller.rb +19 -0
  7. data/app/helpers/job_invocations_helper.rb +2 -2
  8. data/app/helpers/remote_execution_helper.rb +40 -9
  9. data/app/lib/actions/remote_execution/run_host_job.rb +36 -6
  10. data/app/lib/foreman_remote_execution/provider_input.rb +29 -0
  11. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -5
  12. data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
  13. data/app/models/host_proxy_invocation.rb +4 -0
  14. data/app/models/host_status/execution_status.rb +5 -5
  15. data/app/models/invocation_provider_input_value.rb +12 -0
  16. data/app/models/job_invocation.rb +35 -12
  17. data/app/models/job_invocation_composer.rb +74 -19
  18. data/app/models/remote_execution_provider.rb +18 -3
  19. data/app/models/setting/remote_execution.rb +11 -1
  20. data/app/models/ssh_execution_provider.rb +4 -4
  21. data/app/models/targeting.rb +5 -1
  22. data/app/models/template_invocation.rb +2 -0
  23. data/app/overrides/execution_interface.rb +8 -8
  24. data/app/overrides/subnet_proxies.rb +6 -6
  25. data/app/services/renderer_methods.rb +12 -0
  26. data/app/views/job_invocations/_form.html.erb +8 -0
  27. data/app/views/job_invocations/index.html.erb +1 -1
  28. data/app/views/templates/ssh/module_action.erb +1 -0
  29. data/app/views/templates/ssh/puppet_run_once.erb +1 -0
  30. data/config/routes.rb +1 -0
  31. data/db/migrate/20180110104432_rename_template_invocation_permission.rb +1 -1
  32. data/db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb +4 -4
  33. data/db/migrate/20210312074713_add_provider_inputs.rb +10 -0
  34. data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
  35. data/extra/cockpit/foreman-cockpit-session +6 -6
  36. data/foreman_remote_execution.gemspec +1 -1
  37. data/lib/foreman_remote_execution/engine.rb +14 -12
  38. data/lib/foreman_remote_execution/version.rb +1 -1
  39. data/locale/action_names.rb +1 -0
  40. data/locale/de/foreman_remote_execution.po +77 -27
  41. data/locale/en/foreman_remote_execution.po +77 -27
  42. data/locale/en_GB/foreman_remote_execution.po +77 -27
  43. data/locale/es/foreman_remote_execution.po +77 -27
  44. data/locale/foreman_remote_execution.pot +241 -163
  45. data/locale/fr/foreman_remote_execution.po +77 -27
  46. data/locale/ja/foreman_remote_execution.po +77 -27
  47. data/locale/ko/foreman_remote_execution.po +77 -27
  48. data/locale/pt_BR/foreman_remote_execution.po +77 -27
  49. data/locale/ru/foreman_remote_execution.po +77 -27
  50. data/locale/zh_CN/foreman_remote_execution.po +77 -27
  51. data/locale/zh_TW/foreman_remote_execution.po +77 -27
  52. data/package.json +4 -2
  53. data/test/functional/api/v2/job_invocations_controller_test.rb +14 -1
  54. data/test/helpers/remote_execution_helper_test.rb +16 -0
  55. data/test/unit/job_invocation_composer_test.rb +100 -3
  56. data/test/unit/job_invocation_report_template_test.rb +57 -0
  57. data/test/unit/job_invocation_test.rb +1 -1
  58. data/webpack/JobWizard/JobWizard.js +95 -11
  59. data/webpack/JobWizard/JobWizard.scss +53 -0
  60. data/webpack/JobWizard/JobWizardConstants.js +16 -0
  61. data/webpack/JobWizard/JobWizardSelectors.js +47 -0
  62. data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +43 -0
  63. data/webpack/JobWizard/__tests__/fixtures.js +128 -0
  64. data/webpack/JobWizard/__tests__/integration.test.js +84 -0
  65. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +110 -0
  66. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +67 -0
  67. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +195 -0
  68. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +144 -0
  69. data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +23 -0
  70. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +109 -0
  71. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +123 -0
  72. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +94 -0
  73. data/webpack/JobWizard/steps/Schedule/QueryType.js +48 -0
  74. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +61 -0
  75. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +25 -0
  76. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +51 -0
  77. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +22 -0
  78. data/webpack/JobWizard/steps/Schedule/index.js +41 -0
  79. data/webpack/JobWizard/steps/form/FormHelpers.js +20 -0
  80. data/webpack/JobWizard/steps/form/Formatter.js +149 -0
  81. data/webpack/JobWizard/steps/form/GroupedSelectField.js +91 -0
  82. data/webpack/JobWizard/steps/form/NumberInput.js +33 -0
  83. data/webpack/JobWizard/steps/form/SelectField.js +60 -0
  84. data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +76 -0
  85. data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
  86. data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
  87. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +21 -2
  88. data/webpack/__mocks__/foremanReact/redux/API/index.js +5 -0
  89. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
  90. data/webpack/global_index.js +6 -0
  91. data/webpack/index.js +3 -4
  92. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +83 -0
  93. data/webpack/react_app/components/RecentJobsCard/constants.js +1 -0
  94. data/webpack/react_app/components/RecentJobsCard/index.js +1 -0
  95. data/webpack/react_app/components/RecentJobsCard/styles.css +15 -0
  96. data/webpack/react_app/components/RegistrationExtension/RexInterface.js +50 -0
  97. data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +9 -0
  98. data/webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap +35 -0
  99. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +8 -3
  100. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
  101. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +7 -2
  102. data/webpack/react_app/extend/fillRecentJobsCard.js +11 -0
  103. data/webpack/react_app/extend/fillregistrationAdvanced.js +11 -0
  104. data/webpack/react_app/extend/reducers.js +5 -0
  105. metadata +52 -8
  106. data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
  107. data/app/views/api/v2/registration/_form.html.erb +0 -12
  108. data/test/models/orchestration/ssh_test.rb +0 -56
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 799c1e90dc4a413af8f803ce9cce5ba32fbb89bcff68413c1650cfdd94fae5c3
4
- data.tar.gz: f804a0e85491bf08cb9500cf7e8376033b94aff50fb3027785f6e4bb64aee895
3
+ metadata.gz: 8c01aa4397a7c931f0cda22e96e238bded98642267efbd24d450fc27bb656d0a
4
+ data.tar.gz: 4a07bd69223c941761dc3ad16001eeef969612e3f5b648bd168f141e6f087781
5
5
  SHA512:
6
- metadata.gz: dbe304879e6b23a473b6d25f11c7a2937b4aeb20a5053ae8824a04d2644f0794630e9d45f6d2065909fdac0044ec252a691a84d8d8c6b62283f6d9a5a2b17548
7
- data.tar.gz: 2b019cc3d21335a63dae39d3dad2387fc4ce075fb5e51a50eb39957bb7cc0e979def38e401b81ff473552a46b6f588c7ad99158a98bfb76032cff4763633200f
6
+ metadata.gz: 56a50ffebf8864ec4851fc9e74c479a84b3af194502c5945e9c58cacc0068c283aab2d8cbc282a837aad686ebfe137477989fa572dce8e5d9914ae5c06b72736
7
+ data.tar.gz: c70b0c20e50b576841f96e8050b8be366d345758749b3c3d144af8e4364a65796d72a0874c382e3b13c5e73e36ef0d6266a1fa5544bf6b34805cb79de113c572
@@ -64,24 +64,31 @@ module Api
64
64
  param :description_format, String, :required => false, :desc => N_('Override the description format from the template for this invocation only')
65
65
  param :execution_timeout_interval, Integer, :required => false, :desc => N_('Override the timeout interval from the template for this invocation only')
66
66
  param :feature, String, :required => false, :desc => N_('Remote execution feature label that should be triggered, job template assigned to this feature will be used')
67
+
68
+ RemoteExecutionProvider.providers.each_value do |provider|
69
+ next if !provider.respond_to?(:provider_inputs_doc) || provider.provider_inputs_doc.empty?
70
+ doc = provider.provider_inputs_doc
71
+ param doc[:namespace], Hash, doc[:opts] do
72
+ doc[:children].map do |input|
73
+ param input[:name], input[:type], input[:opts]
74
+ end
75
+ end
76
+ end
67
77
  end
68
78
  end
69
79
 
70
80
  api :POST, '/job_invocations/', N_('Create a job invocation')
71
81
  param_group :job_invocation, :as => :create
72
82
  def create
73
- if job_invocation_params[:feature].present?
74
- composer = composer_for_feature
75
- else
76
- validate_template
77
- composer = JobInvocationComposer.from_api_params(
78
- job_invocation_params
79
- )
80
- end
83
+ composer = JobInvocationComposer.from_api_params(
84
+ job_invocation_params
85
+ )
81
86
  composer.trigger!
82
87
  @job_invocation = composer.job_invocation
83
88
  @hosts = @job_invocation.targeting.hosts
84
89
  process_response @job_invocation
90
+ rescue JobInvocationComposer::JobTemplateNotFound, JobInvocationComposer::FeatureNotFound => e
91
+ not_found(error: { message: e.message })
85
92
  end
86
93
 
87
94
  api :GET, '/job_invocations/:id/hosts/:host_id', N_('Get output for a host')
@@ -118,7 +125,7 @@ module Api
118
125
  render :json => { :cancelled => result, :id => @job_invocation.id }
119
126
  else
120
127
  render :json => { :message => _('The job could not be cancelled.') },
121
- :status => :unprocessable_entity
128
+ :status => :unprocessable_entity
122
129
  end
123
130
  end
124
131
 
@@ -133,7 +140,7 @@ module Api
133
140
  process_response @job_invocation
134
141
  else
135
142
  render :json => { :error => _('Could not rerun job %{id} because its template could not be found') % { :id => composer.reruns } },
136
- :status => :not_found
143
+ :status => :not_found
137
144
  end
138
145
  end
139
146
 
@@ -180,30 +187,28 @@ module Api
180
187
  not_found({ :error => { :message => (_("Host with id '%{id}' was not found") % { :id => params['host_id'] }) } })
181
188
  end
182
189
 
183
- def validate_template
184
- JobTemplate.authorized(:view_job_templates).find(job_invocation_params['job_template_id'])
185
- rescue ActiveRecord::RecordNotFound
186
- not_found({ :error => { :message => (_("Template with id '%{id}' was not found") % { :id => job_invocation_params['job_template_id'] }) } })
187
- end
188
-
189
190
  def job_invocation_params
190
191
  return @job_invocation_params if @job_invocation_params.present?
191
192
 
192
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
+
193
199
  if job_invocation_params.key?(:ssh)
194
200
  job_invocation_params.merge!(job_invocation_params.delete(:ssh).permit(:effective_user))
195
201
  end
202
+
196
203
  job_invocation_params[:inputs] ||= {}
197
204
  job_invocation_params[:inputs].permit!
205
+ permit_provider_inputs job_invocation_params
198
206
  @job_invocation_params = job_invocation_params
199
207
  end
200
208
 
201
- def composer_for_feature
202
- JobInvocationComposer.for_feature(
203
- job_invocation_params[:feature],
204
- job_invocation_params[:host_ids],
205
- job_invocation_params[:inputs].to_hash
206
- )
209
+ def permit_provider_inputs(invocation_params)
210
+ providers = RemoteExecutionProvider.providers.values.reject { |provider| !provider.respond_to?(:provider_input_namespace) || provider.provider_input_namespace.empty? }
211
+ providers.each { |provider| invocation_params[provider.provider_input_namespace]&.permit! }
207
212
  end
208
213
 
209
214
  def output_lines_since(task, time)
@@ -0,0 +1,19 @@
1
+ module ForemanRemoteExecution
2
+ module Concerns
3
+ module Api
4
+ module V2
5
+ module RegistrationCommandsControllerExtensions
6
+ module ApipieExtensions
7
+ extend Apipie::DSL::Concern
8
+
9
+ update_api(:create) do
10
+ param :registration_command, Hash do
11
+ param :remote_execution_interface, String, desc: N_("Identifier of the Host interface for Remote execution")
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -67,7 +67,7 @@ class JobInvocationsController < ApplicationController
67
67
  end
68
68
 
69
69
  def index
70
- @job_invocations = resource_base_search_and_page.with_task.order('job_invocations.id DESC')
70
+ @job_invocations = resource_base_search_and_page.preload(:task, :targeting).order('job_invocations.id DESC')
71
71
  end
72
72
 
73
73
  # refreshes the form
@@ -24,10 +24,10 @@ class JobTemplatesController < ::TemplatesController
24
24
  render :plain => output
25
25
  else
26
26
  render status: :not_acceptable,
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
- )
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,25 @@ 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
+ advanced_template_inputs, template_inputs = map_template_inputs(job_template.template_inputs_with_foreign).partition { |x| x["advanced"] }
14
+ render :json => {
15
+ :job_template => job_template,
16
+ :effective_user => job_template.effective_user,
17
+ :template_inputs => template_inputs,
18
+ :advanced_template_inputs => advanced_template_inputs,
19
+ }
20
+ end
21
+
22
+ def resource_name(nested_resource = nil)
23
+ nested_resource || 'job_template'
24
+ end
25
+
26
+ def map_template_inputs(template_inputs_with_foreign)
27
+ template_inputs_with_foreign.map { |input| input.attributes.merge({:resource_type => input.resource_type&.tableize }) }
28
+ end
29
+
11
30
  def resource_class
12
31
  JobTemplate
13
32
  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
- :locals => { :template_invocation => template_invocation,
38
- :target => host })
37
+ :locals => { :template_invocation => template_invocation,
38
+ :target => host })
39
39
  end.reduce(:+)
40
40
  end
41
41
 
@@ -7,6 +7,10 @@ module RemoteExecutionHelper
7
7
  @job_hosts_authorizer ||= Authorizer.new(User.current, :collection => @hosts)
8
8
  end
9
9
 
10
+ def host_tasks_authorizer
11
+ @host_tasks_authorizer ||= Authorizer.new(User.current, :collection => @job_invocation.sub_tasks)
12
+ end
13
+
10
14
  def host_counter(label, count)
11
15
  content_tag(:div, :class => 'host_counter') do
12
16
  content_tag(:div, label, :class => 'header') + content_tag(:div, count.to_s, :class => 'count')
@@ -27,19 +31,19 @@ module RemoteExecutionHelper
27
31
 
28
32
  if authorized_for(hash_for_host_path(host).merge(auth_object: host, permission: :view_hosts, authorizer: job_hosts_authorizer))
29
33
  links << { title: _('Host detail'),
30
- action: { href: host_path(host), 'data-method': 'get', id: "#{host.name}-actions-detail" } }
34
+ action: { href: host_path(host), 'data-method': 'get', id: "#{host.name}-actions-detail" } }
31
35
  end
32
36
 
33
37
  if authorized_for(hash_for_rerun_job_invocation_path(id: job_invocation, host_ids: [ host.id ], authorizer: job_hosts_authorizer))
34
38
  links << { title: (_('Rerun on %s') % host.name),
35
- action: { href: rerun_job_invocation_path(job_invocation, host_ids: [ host.id ]),
36
- 'data-method': 'get', id: "#{host.name}-actions-rerun" } }
39
+ action: { href: rerun_job_invocation_path(job_invocation, host_ids: [ host.id ]),
40
+ 'data-method': 'get', id: "#{host.name}-actions-rerun" } }
37
41
  end
38
42
 
39
- if host_task.present? && authorized_for(hash_for_foreman_tasks_task_path(host_task).merge(auth_object: host_task, permission: :view_foreman_tasks))
43
+ if host_task.present? && authorized_for(hash_for_foreman_tasks_task_path(host_task).merge(auth_object: host_task, permission: :view_foreman_tasks, authorizer: host_tasks_authorizer))
40
44
  links << { title: _('Host task'),
41
- action: { href: foreman_tasks_task_path(host_task),
42
- 'data-method': 'get', id: "#{host.name}-actions-task" } }
45
+ action: { href: foreman_tasks_task_path(host_task),
46
+ 'data-method': 'get', id: "#{host.name}-actions-task" } }
43
47
  end
44
48
 
45
49
  links
@@ -60,6 +64,12 @@ module RemoteExecutionHelper
60
64
  job_invocation = task.task_groups.find { |group| group.class == JobInvocationTaskGroup }.job_invocation
61
65
  task_authorizer = Authorizer.new(User.current, :collection => [task])
62
66
  buttons = []
67
+ if (template = job_report_template) && authorized_for(controller: :report_templates, action: :generate)
68
+ buttons << link_to(_('Create Report'), generate_report_template_path(template, job_report_template_parameters(job_invocation, template)),
69
+ class: 'btn btn-default',
70
+ title: _('Create report for this job'),
71
+ disabled: task.pending?)
72
+ end
63
73
  if authorized_for(hash_for_new_job_invocation_path)
64
74
  buttons << link_to(_('Rerun'), rerun_job_invocation_path(:id => job_invocation.id),
65
75
  :class => 'btn btn-default',
@@ -153,11 +163,11 @@ module RemoteExecutionHelper
153
163
  content_tag :pre, preview
154
164
  elsif target.nil?
155
165
  alert :text => _('Could not render the preview because no host matches the search query.'),
156
- :class => 'alert alert-block alert-warning base',
157
- :close => false
166
+ :class => 'alert alert-block alert-warning base',
167
+ :close => false
158
168
  else
159
169
  alert :class => 'alert-block alert-danger base in fade has-error',
160
- :text => renderer.error_message.html_safe # rubocop:disable Rails/OutputSafety
170
+ :text => renderer.error_message.html_safe # rubocop:disable Rails/OutputSafety
161
171
  end
162
172
  end
163
173
 
@@ -230,6 +240,27 @@ module RemoteExecutionHelper
230
240
  task.execution_plan.actions[1].try(:input).try(:[], 'script')
231
241
  end
232
242
 
243
+ def job_report_template
244
+ template = ReportTemplate.where(name: Setting['remote_execution_job_invocation_report_template']).first
245
+
246
+ template if template.template_inputs.where(name: 'job_id').exists?
247
+ end
248
+
249
+ def job_report_template_parameters(job_invocation, template)
250
+ template_input = template.template_inputs.where(name: 'job_id').first
251
+ raise "#job_report_template_parameters need template that has 'job_id' input" unless template_input
252
+
253
+ {
254
+ report_template_report: {
255
+ input_values: {
256
+ "#{template_input.id}": {
257
+ value: job_invocation.id,
258
+ },
259
+ },
260
+ },
261
+ }
262
+ end
263
+
233
264
  def targeting_hosts(job_invocation, hosts)
234
265
  hosts.map do |host|
235
266
  template_invocation = job_invocation.template_invocations.find { |template_inv| template_inv.host_id == host.id }
@@ -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
- action_subject(host, :job_category => job_invocation.job_category, :description => job_invocation.description, :job_invocation_id => job_invocation.id)
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
@@ -40,12 +47,16 @@ module Actions
40
47
  script = renderer.render
41
48
  raise _('Failed rendering template: %s') % renderer.error_message unless script
42
49
 
50
+ first_execution = host.executed_through_proxies.where(:id => proxy.id).none?
51
+ host.executed_through_proxies << proxy if first_execution
52
+
43
53
  additional_options = { :hostname => provider.find_ip_or_hostname(host),
44
54
  :script => script,
45
55
  :execution_timeout_interval => job_invocation.execution_timeout_interval,
46
56
  :secrets => secrets(host, job_invocation, provider),
47
57
  :use_batch_triggering => true,
48
- :use_concurrency_control => options[:use_concurrency_control]}
58
+ :use_concurrency_control => options[:use_concurrency_control],
59
+ :first_execution => first_execution }
49
60
  action_options = provider.proxy_command_options(template_invocation, host)
50
61
  .merge(additional_options)
51
62
 
@@ -58,6 +69,22 @@ module Actions
58
69
  check_exit_status
59
70
  end
60
71
 
72
+ def self.feature_job_event_name(label, suffix = :success)
73
+ ::Foreman::Observable.event_name_for("#{::Actions::RemoteExecution::RunHostJob.event_name_base}_#{label}_#{::Actions::RemoteExecution::RunHostJob.event_name_suffix(suffix)}")
74
+ end
75
+
76
+ def emit_feature_event(execution_plan, hook = :success)
77
+ return unless root_action?
78
+
79
+ payload = event_payload(execution_plan)
80
+ if input["job_features"]&.any?
81
+ input['job_features'].each do |feature|
82
+ name = "#{self.class.event_name_base}_#{feature}_#{self.class.event_name_suffix(hook)}"
83
+ trigger_hook name, payload: payload
84
+ end
85
+ end
86
+ end
87
+
61
88
  def secrets(host, job_invocation, provider)
62
89
  job_secrets = { :ssh_password => job_invocation.password,
63
90
  :key_passphrase => job_invocation.key_passphrase,
@@ -78,7 +105,7 @@ module Actions
78
105
 
79
106
  def humanized_input
80
107
  N_('%{description} on %{host}') % { :host => input[:host].try(:[], :name),
81
- :description => input[:description].try(:capitalize) || input[:job_category] }
108
+ :description => input[:description].try(:capitalize) || input[:job_category] }
82
109
  end
83
110
 
84
111
  def humanized_name
@@ -186,10 +213,13 @@ module Actions
186
213
  'All %{count} applicable proxies are down. Tried %{proxy_names}',
187
214
  offline_proxies.count) % settings
188
215
  elsif proxy == :not_defined
189
- settings = { :global_proxy => 'remote_execution_global_proxy',
190
- :fallback_proxy => 'remote_execution_fallback_proxy' }
216
+ settings = {
217
+ global_proxy: 'remote_execution_global_proxy',
218
+ fallback_proxy: 'remote_execution_fallback_proxy',
219
+ provider: provider,
220
+ }
191
221
 
192
- raise _('Could not use any proxy. Consider configuring %{global_proxy}, ' +
222
+ raise _('Could not use any proxy for the %{provider} job. Consider configuring %{global_proxy}, ' +
193
223
  '%{fallback_proxy} in settings') % settings
194
224
  end
195
225
  proxy
@@ -0,0 +1,29 @@
1
+ module ForemanRemoteExecution
2
+ class ProviderInput
3
+ attr_reader :name, :label, :description, :options, :value_type, :required
4
+ attr_accessor :value
5
+
6
+ def initialize(name:, label:, value:, description: nil, options: nil, value_type: nil, required: false, hidden: false)
7
+ @name = name
8
+ @label = label
9
+ @value = value
10
+ @description = description
11
+ @options = options
12
+ @value_type = value_type
13
+ @required = required
14
+ @hidden = hidden
15
+ end
16
+
17
+ def template_input
18
+ self
19
+ end
20
+
21
+ def hidden_value?
22
+ @hidden
23
+ end
24
+
25
+ def options_array
26
+ options.blank? ? [] : options.split(/\r?\n/).map(&:strip)
27
+ end
28
+ end
29
+ end
@@ -6,17 +6,19 @@ module ForemanRemoteExecution
6
6
  has_many :template_invocations, :dependent => :destroy, :foreign_key => 'host_id'
7
7
  has_one :execution_status_object, :class_name => 'HostStatus::ExecutionStatus', :foreign_key => 'host_id', :dependent => :destroy
8
8
  has_many :run_host_job_tasks, :through => :template_invocations
9
+ has_many :host_proxy_invocations, :foreign_key => 'host_id', :dependent => :destroy
10
+ has_many :executed_through_proxies, :through => :host_proxy_invocations, :source => 'smart_proxy'
9
11
 
10
12
  scoped_search :relation => :run_host_job_tasks, :on => :result, :rename => 'job_invocation.result',
11
- :ext_method => :search_by_job_invocation,
12
- :only_explicit => true,
13
- :complete_value => TemplateInvocation::TaskResultMap::REVERSE_MAP
13
+ :ext_method => :search_by_job_invocation,
14
+ :only_explicit => true,
15
+ :complete_value => TemplateInvocation::TaskResultMap::REVERSE_MAP
14
16
 
15
17
  scoped_search :relation => :template_invocations, :on => :job_invocation_id,
16
- :rename => 'job_invocation.id', :only_explicit => true, :ext_method => :search_by_job_invocation
18
+ :rename => 'job_invocation.id', :only_explicit => true, :ext_method => :search_by_job_invocation
17
19
 
18
20
  scoped_search :relation => :execution_status_object, :on => :status, :rename => :execution_status,
19
- :complete_value => { :ok => HostStatus::ExecutionStatus::OK, :error => HostStatus::ExecutionStatus::ERROR }
21
+ :complete_value => { :ok => HostStatus::ExecutionStatus::OK, :error => HostStatus::ExecutionStatus::ERROR }
20
22
 
21
23
  def search_by_job_invocation(key, operator, value)
22
24
  if key == 'job_invocation.result'
@@ -1,5 +1,11 @@
1
1
  module ForemanRemoteExecution
2
2
  module SmartProxyExtensions
3
+ def self.prepended(base)
4
+ base.instance_eval do
5
+ has_many :host_proxy_invocations, :dependent => :destroy
6
+ end
7
+ end
8
+
3
9
  def pubkey
4
10
  self[:pubkey] || update_pubkey
5
11
  end
@@ -0,0 +1,4 @@
1
+ class HostProxyInvocation < ApplicationRecord
2
+ belongs_to :host, :class_name => 'Host::Managed', :inverse_of => :host_proxy_invocations
3
+ belongs_to :smart_proxy
4
+ end