foreman_remote_execution 4.6.0 → 5.0.1

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_ci.yml +7 -0
  3. data/.rubocop_todo.yml +1 -0
  4. data/app/controllers/api/v2/job_invocations_controller.rb +16 -1
  5. data/app/controllers/job_invocations_controller.rb +1 -1
  6. data/app/controllers/ui_job_wizard_controller.rb +21 -2
  7. data/app/graphql/mutations/job_invocations/create.rb +43 -0
  8. data/app/graphql/types/job_invocation.rb +16 -0
  9. data/app/graphql/types/job_invocation_input.rb +13 -0
  10. data/app/graphql/types/recurrence_input.rb +8 -0
  11. data/app/graphql/types/scheduling_input.rb +6 -0
  12. data/app/graphql/types/targeting_enum.rb +7 -0
  13. data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +5 -1
  14. data/app/helpers/remote_execution_helper.rb +9 -3
  15. data/app/lib/actions/remote_execution/run_host_job.rb +10 -1
  16. data/app/lib/actions/remote_execution/run_hosts_job.rb +58 -4
  17. data/app/mailers/rex_job_mailer.rb +15 -0
  18. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +10 -0
  19. data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
  20. data/app/models/host_proxy_invocation.rb +4 -0
  21. data/app/models/host_status/execution_status.rb +3 -3
  22. data/app/models/job_invocation.rb +12 -5
  23. data/app/models/job_invocation_composer.rb +25 -17
  24. data/app/models/job_template.rb +1 -1
  25. data/app/models/remote_execution_feature.rb +5 -1
  26. data/app/models/remote_execution_provider.rb +18 -2
  27. data/app/models/rex_mail_notification.rb +13 -0
  28. data/app/models/targeting.rb +7 -3
  29. data/app/services/ui_notifications/remote_execution_jobs/base_job_finish.rb +2 -1
  30. data/app/views/dashboard/_latest-jobs.html.erb +21 -0
  31. data/app/views/job_invocations/index.html.erb +1 -1
  32. data/app/views/job_invocations/refresh.js.erb +1 -0
  33. data/app/views/rex_job_mailer/job_finished.html.erb +24 -0
  34. data/app/views/rex_job_mailer/job_finished.text.erb +9 -0
  35. data/app/views/template_invocations/show.html.erb +2 -1
  36. data/app/views/templates/ssh/module_action.erb +1 -0
  37. data/app/views/templates/ssh/power_action.erb +2 -0
  38. data/app/views/templates/ssh/puppet_run_once.erb +1 -0
  39. data/config/routes.rb +1 -0
  40. data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
  41. data/db/migrate/20210816100932_rex_setting_category_to_dsl.rb +5 -0
  42. data/db/seeds.d/50-notification_blueprints.rb +14 -0
  43. data/db/seeds.d/95-mail_notifications.rb +24 -0
  44. data/foreman_remote_execution.gemspec +2 -3
  45. data/lib/foreman_remote_execution/engine.rb +114 -8
  46. data/lib/foreman_remote_execution/version.rb +1 -1
  47. data/package.json +9 -7
  48. data/test/functional/api/v2/job_invocations_controller_test.rb +20 -0
  49. data/test/functional/cockpit_controller_test.rb +0 -1
  50. data/test/graphql/mutations/job_invocations/create.rb +58 -0
  51. data/test/graphql/queries/job_invocation_query_test.rb +31 -0
  52. data/test/graphql/queries/job_invocations_query_test.rb +35 -0
  53. data/test/helpers/remote_execution_helper_test.rb +0 -1
  54. data/test/unit/actions/run_host_job_test.rb +21 -0
  55. data/test/unit/actions/run_hosts_job_test.rb +99 -4
  56. data/test/unit/concerns/host_extensions_test.rb +40 -7
  57. data/test/unit/input_template_renderer_test.rb +1 -89
  58. data/test/unit/job_invocation_composer_test.rb +18 -18
  59. data/test/unit/job_invocation_report_template_test.rb +16 -13
  60. data/test/unit/job_invocation_test.rb +1 -1
  61. data/test/unit/job_template_effective_user_test.rb +0 -4
  62. data/test/unit/remote_execution_provider_test.rb +46 -4
  63. data/test/unit/targeting_test.rb +68 -1
  64. data/webpack/JobWizard/JobWizard.js +158 -24
  65. data/webpack/JobWizard/JobWizard.scss +93 -1
  66. data/webpack/JobWizard/JobWizardConstants.js +54 -0
  67. data/webpack/JobWizard/JobWizardSelectors.js +41 -0
  68. data/webpack/JobWizard/__tests__/fixtures.js +188 -3
  69. data/webpack/JobWizard/__tests__/integration.test.js +41 -106
  70. data/webpack/JobWizard/__tests__/validation.test.js +141 -0
  71. data/webpack/JobWizard/autofill.js +38 -0
  72. data/webpack/JobWizard/index.js +7 -0
  73. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +41 -10
  74. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +90 -0
  75. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +116 -55
  76. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +354 -16
  77. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +79 -246
  78. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +5 -2
  79. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +123 -51
  80. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -2
  81. data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +62 -0
  82. data/webpack/JobWizard/steps/HostsAndInputs/HostSearch.js +54 -0
  83. data/webpack/JobWizard/steps/HostsAndInputs/SelectAPI.js +33 -0
  84. data/webpack/JobWizard/steps/HostsAndInputs/SelectGQL.js +52 -0
  85. data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +100 -0
  86. data/webpack/JobWizard/steps/HostsAndInputs/TemplateInputs.js +23 -0
  87. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +151 -0
  88. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +53 -0
  89. data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +18 -0
  90. data/webpack/JobWizard/steps/HostsAndInputs/hostgroups.gql +8 -0
  91. data/webpack/JobWizard/steps/HostsAndInputs/hosts.gql +8 -0
  92. data/webpack/JobWizard/steps/HostsAndInputs/index.js +214 -0
  93. data/webpack/JobWizard/steps/ReviewDetails/index.js +193 -0
  94. data/webpack/JobWizard/steps/Schedule/PurposeField.js +31 -0
  95. data/webpack/JobWizard/steps/Schedule/QueryType.js +51 -0
  96. data/webpack/JobWizard/steps/Schedule/RepeatCron.js +53 -0
  97. data/webpack/JobWizard/steps/Schedule/RepeatDaily.js +37 -0
  98. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +54 -0
  99. data/webpack/JobWizard/steps/Schedule/RepeatMonth.js +46 -0
  100. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +125 -0
  101. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +70 -0
  102. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +28 -0
  103. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +106 -0
  104. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +402 -0
  105. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +32 -0
  106. data/webpack/JobWizard/steps/Schedule/index.js +178 -0
  107. data/webpack/JobWizard/steps/form/DateTimePicker.js +126 -0
  108. data/webpack/JobWizard/steps/form/FormHelpers.js +5 -0
  109. data/webpack/JobWizard/steps/form/Formatter.js +181 -0
  110. data/webpack/JobWizard/steps/form/NumberInput.js +36 -0
  111. data/webpack/JobWizard/steps/form/ResourceSelect.js +29 -0
  112. data/webpack/JobWizard/steps/form/SearchSelect.js +121 -0
  113. data/webpack/JobWizard/steps/form/SelectField.js +28 -5
  114. data/webpack/JobWizard/steps/form/WizardTitle.js +14 -0
  115. data/webpack/JobWizard/steps/form/__tests__/SelectSearch.test.js +33 -0
  116. data/webpack/JobWizard/submit.js +120 -0
  117. data/webpack/JobWizard/validation.js +53 -0
  118. data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext/index.js +2 -0
  119. data/webpack/__mocks__/foremanReact/common/I18n.js +2 -0
  120. data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteActions.js +1 -0
  121. data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteConstants.js +1 -0
  122. data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
  123. data/webpack/__mocks__/foremanReact/routes/RouterSelector.js +1 -0
  124. data/webpack/helpers.js +1 -0
  125. data/webpack/react_app/components/RecentJobsCard/JobStatusIcon.js +43 -0
  126. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +73 -66
  127. data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +98 -0
  128. data/webpack/react_app/components/RecentJobsCard/constants.js +11 -0
  129. data/webpack/react_app/components/RecentJobsCard/styles.scss +11 -0
  130. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
  131. data/webpack/react_app/extend/fillRecentJobsCard.js +1 -1
  132. metadata +71 -16
  133. data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
  134. data/app/models/setting/remote_execution.rb +0 -88
  135. data/test/models/orchestration/ssh_test.rb +0 -56
  136. data/webpack/JobWizard/__tests__/JobWizard.test.js +0 -13
  137. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -32
  138. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +0 -113
  139. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +0 -38
  140. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +0 -23
  141. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +0 -37
  142. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +0 -23
  143. data/webpack/react_app/components/RecentJobsCard/styles.css +0 -15
@@ -121,7 +121,10 @@ class JobInvocationComposer
121
121
  :targeting => targeting_params,
122
122
  :triggering => triggering_params,
123
123
  :description_format => api_params[:description_format],
124
+ :password => api_params[:password],
124
125
  :remote_execution_feature_id => remote_execution_feature_id,
126
+ :effective_user_password => api_params[:effective_user_password],
127
+ :key_passphrase => api_params[:key_passphrase],
125
128
  :concurrency_control => concurrency_control_params,
126
129
  :execution_timeout_interval => api_params[:execution_timeout_interval] || template.execution_timeout_interval,
127
130
  :template_invocations => template_invocations_params }.with_indifferent_access
@@ -138,17 +141,11 @@ class JobInvocationComposer
138
141
  end
139
142
 
140
143
  def triggering_params
141
- raise ::Foreman::Exception, _('Cannot specify both recurrence and scheduling') if api_params[:recurrence].present? && api_params[:scheduling].present?
142
-
143
- if api_params[:recurrence].present?
144
- {
145
- :mode => :recurring,
146
- :cronline => api_params[:recurrence][:cron_line],
147
- :end_time => format_datetime(api_params[:recurrence][:end_time]),
148
- :input_type => :cronline,
149
- :max_iteration => api_params[:recurrence][:max_iteration],
150
- }
151
- elsif api_params[:scheduling].present?
144
+ if api_params[:recurrence].present? && api_params[:scheduling].present?
145
+ recurring_mode_params.merge :start_at_raw => format_datetime(api_params[:scheduling][:start_at])
146
+ elsif api_params[:recurrence].present? && api_params[:scheduling].empty?
147
+ recurring_mode_params
148
+ elsif api_params[:recurrence].empty? && api_params[:scheduling].present?
152
149
  {
153
150
  :mode => :future,
154
151
  :start_at_raw => format_datetime(api_params[:scheduling][:start_at]),
@@ -160,6 +157,17 @@ class JobInvocationComposer
160
157
  end
161
158
  end
162
159
 
160
+ def recurring_mode_params
161
+ {
162
+ :mode => :recurring,
163
+ :cronline => api_params[:recurrence][:cron_line],
164
+ :end_time => format_datetime(api_params[:recurrence][:end_time]),
165
+ :input_type => :cronline,
166
+ :max_iteration => api_params[:recurrence][:max_iteration],
167
+ :purpose => api_params[:recurrence][:purpose],
168
+ }
169
+ end
170
+
163
171
  def concurrency_control_params
164
172
  {
165
173
  :level => api_params.fetch(:concurrency_control, {})[:concurrency_level],
@@ -209,7 +217,7 @@ class JobInvocationComposer
209
217
  def format_datetime(datetime)
210
218
  return datetime if datetime.blank?
211
219
 
212
- Time.parse(datetime).strftime('%Y-%m-%d %H:%M')
220
+ Time.parse(datetime).utc.strftime('%Y-%m-%d %H:%M')
213
221
  end
214
222
  end
215
223
 
@@ -296,7 +304,7 @@ class JobInvocationComposer
296
304
  attr_reader :feature_label, :feature, :provided_inputs
297
305
 
298
306
  def initialize(feature_label, hosts, provided_inputs = {})
299
- @feature = RemoteExecutionFeature.feature(feature_label)
307
+ @feature = RemoteExecutionFeature.feature!(feature_label)
300
308
  @provided_inputs = provided_inputs
301
309
  translator = HostIdsTranslator.new(hosts)
302
310
  @host_bookmark = translator.bookmark
@@ -405,7 +413,7 @@ class JobInvocationComposer
405
413
  job_invocation.effective_user_password = params[:effective_user_password]
406
414
 
407
415
  if @reruns && job_invocation.targeting.static?
408
- job_invocation.targeting.host_ids = JobInvocation.find(@reruns).targeting.host_ids
416
+ job_invocation.targeting.assign_host_ids(JobInvocation.find(@reruns).targeting.host_ids)
409
417
  job_invocation.targeting.mark_resolved!
410
418
  end
411
419
 
@@ -513,14 +521,14 @@ class JobInvocationComposer
513
521
  end
514
522
 
515
523
  def available_bookmarks
516
- Bookmark.authorized(:view_bookmarks).my_bookmarks.where(:controller => ['hosts', 'dashboard'])
524
+ Bookmark.my_bookmarks.where(:controller => ['hosts', 'dashboard'])
517
525
  end
518
526
 
519
527
  def targeted_hosts
520
528
  if displayed_search_query.blank?
521
529
  Host.where('1 = 0')
522
530
  else
523
- Host.authorized(Targeting::RESOLVE_PERMISSION, Host).search_for(displayed_search_query)
531
+ Host.execution_scope.authorized(Targeting::RESOLVE_PERMISSION, Host).search_for(displayed_search_query)
524
532
  end
525
533
  end
526
534
 
@@ -636,7 +644,7 @@ class JobInvocationComposer
636
644
  setting_value = Setting['remote_execution_form_job_template']
637
645
  return default_value unless setting_value
638
646
 
639
- form_template = JobTemplate.find_by :name => setting_value
647
+ form_template = JobTemplate.authorized(:view_job_templates).find_by :name => setting_value
640
648
  return default_value unless form_template
641
649
 
642
650
  if block_given?
@@ -83,7 +83,7 @@ class JobTemplate < ::Template
83
83
  end
84
84
 
85
85
  def acceptable_template_input_types
86
- [ :user, :fact, :variable, :puppet_parameter ]
86
+ [ :user, :fact, :variable ]
87
87
  end
88
88
 
89
89
  def default_render_scope_class
@@ -20,7 +20,11 @@ class RemoteExecutionFeature < ApplicationRecord
20
20
  end
21
21
 
22
22
  def self.feature(label)
23
- self.find_by(label: label) || raise(::Foreman::Exception.new(N_('Unknown remote execution feature %s'), label))
23
+ self.find_by(label: label)
24
+ end
25
+
26
+ def self.feature!(label)
27
+ feature(label) || raise(::Foreman::Exception.new(N_('Unknown remote execution feature %s'), label))
24
28
  end
25
29
 
26
30
  def self.register(label, name, options = {})
@@ -80,7 +80,14 @@ class RemoteExecutionProvider
80
80
 
81
81
  def find_ip(host, interfaces)
82
82
  if host_setting(host, :remote_execution_connect_by_ip)
83
- interfaces.find { |i| i.ip.present? }.try(:ip)
83
+ ip4_address = interfaces.find { |i| i.ip.present? }.try(:ip)
84
+ ip6_address = interfaces.find { |i| i.ip6.present? }.try(:ip6)
85
+
86
+ if host_setting(host, :remote_execution_connect_by_ip_prefer_ipv6)
87
+ ip6_address || ip4_address
88
+ else
89
+ ip4_address || ip6_address
90
+ end
84
91
  end
85
92
  end
86
93
 
@@ -89,7 +96,8 @@ class RemoteExecutionProvider
89
96
  end
90
97
 
91
98
  def host_setting(host, setting)
92
- host.host_param(setting.to_s) || Setting[setting]
99
+ param_value = host.host_param(setting.to_s)
100
+ param_value.nil? ? Setting[setting] : param_value
93
101
  end
94
102
 
95
103
  def ssh_password(_host)
@@ -114,6 +122,10 @@ class RemoteExecutionProvider
114
122
  'Proxy::RemoteExecution::Ssh::Actions::RunScript'
115
123
  end
116
124
 
125
+ def proxy_batch_size
126
+ Setting['foreman_tasks_proxy_batch_size']
127
+ end
128
+
117
129
  # Return a specific proxy selector to use for running a given template
118
130
  # Returns either nil to use the default selector or an instance of a (sub)class of ::ForemanTasks::ProxySelector
119
131
  def required_proxy_selector_for(template)
@@ -123,5 +135,9 @@ class RemoteExecutionProvider
123
135
  ::DefaultProxyProxySelector.new
124
136
  end
125
137
  end
138
+
139
+ def alternative_names(host)
140
+ { :fqdn => find_fqdn(effective_interfaces(host)) }
141
+ end
126
142
  end
127
143
  end
@@ -0,0 +1,13 @@
1
+ class RexMailNotification < MailNotification
2
+ FAILED_JOBS = N_("Subscribe to my failed jobs")
3
+ SUCCEEDED_JOBS = N_("Subscribe to my succeeded jobs")
4
+ ALL_JOBS = N_("Subscribe to all my jobs")
5
+
6
+ def subscription_options
7
+ [
8
+ FAILED_JOBS,
9
+ SUCCEEDED_JOBS,
10
+ ALL_JOBS,
11
+ ]
12
+ end
13
+ end
@@ -44,11 +44,15 @@ class Targeting < ApplicationRecord
44
44
  self.validate!
45
45
  # avoid validation of hosts objects - they will be loaded for no reason.
46
46
  # pluck(:id) returns duplicate results for HostCollections
47
- host_ids = User.as(user.login) { Host.authorized(RESOLVE_PERMISSION, Host).search_for(search_query).order(:name, :id).pluck(:id).uniq }
47
+ host_ids = User.as(user.login) { Host.execution_scope.authorized(RESOLVE_PERMISSION, Host).search_for(search_query).order(:name, :id).pluck(:id).uniq }
48
48
  host_ids.shuffle!(random: Random.new) if randomized_ordering
49
+ self.assign_host_ids(host_ids)
50
+ self.save(:validate => false)
51
+ end
52
+
53
+ def assign_host_ids(host_ids)
49
54
  # this can be optimized even more, by introducing bulk insert
50
55
  self.targeting_hosts.build(host_ids.map { |id| { :host_id => id } })
51
- self.save(:validate => false)
52
56
  end
53
57
 
54
58
  def dynamic?
@@ -62,7 +66,7 @@ class Targeting < ApplicationRecord
62
66
  def self.build_query_from_hosts(ids)
63
67
  return '' if ids.empty?
64
68
 
65
- hosts = Host.where(:id => ids).distinct.pluck(:name)
69
+ hosts = Host.execution_scope.where(:id => ids).distinct.pluck(:name)
66
70
  "name ^ (#{hosts.join(', ')})"
67
71
  end
68
72
 
@@ -20,7 +20,8 @@ module UINotifications
20
20
  end
21
21
 
22
22
  def blueprint
23
- @blueprint ||= NotificationBlueprint.unscoped.find_by(:name => 'rex_job_succeeded')
23
+ blueprint = @subject.status == HostStatus::ExecutionStatus::ERROR ? 'rex_job_failed' : 'rex_job_succeeded'
24
+ @blueprint ||= NotificationBlueprint.unscoped.find_by(:name => blueprint)
24
25
  end
25
26
 
26
27
  def message
@@ -0,0 +1,21 @@
1
+ <h4 class="header">
2
+ <%= link_to _("Latest Jobs"), job_invocations_path(:order=>'start_at DESC') %>
3
+ </h4>
4
+ <% if JobInvocation.latest_jobs.any? %>
5
+ <table class="<%= table_css_classes('table-fixed') %>">
6
+ <tr>
7
+ <th class="col-md-5"><%= _("Name") %></th>
8
+ <th class="col-md-2"><%= _("State") %></th>
9
+ <th class="col-md-3"><%= _("Started") %></th>
10
+ </tr>
11
+ <% JobInvocation.latest_jobs.each do |invocation| %>
12
+ <tr>
13
+ <td class="ellipsis"><%= link_to_if_authorized invocation_description(invocation), hash_for_job_invocation_path(invocation).merge(:auth_object => invocation, :permission => :view_job_invocations, :authorizer => authorizer) %></td>
14
+ <td><%= link_to_invocation_task_if_authorized(invocation) %></td>
15
+ <td><%= time_in_words_span(invocation.start_at) %></td>
16
+ </tr>
17
+ <% end %>
18
+ </table>
19
+ <% else %>
20
+ <p class="ca"><%= _("No jobs available") %></p>
21
+ <% end %>
@@ -21,7 +21,7 @@
21
21
  <% @job_invocations.each do |invocation| %>
22
22
  <tr>
23
23
  <td class="text_warp"><%= link_to_if_authorized invocation_description(invocation), hash_for_job_invocation_path(invocation).merge(:auth_object => invocation, :permission => :view_job_invocations, :authorizer => authorizer) %></td>
24
- <td><%= trunc_with_tooltip(invocation&.targeting&.search_query, 15) %></td>
24
+ <td><%= trunc_with_tooltip(invocation.targeting.search_query, 15) %></td>
25
25
  <td><%= link_to_invocation_task_if_authorized(invocation) %></td>
26
26
  <td><%= invocation_result(invocation, :success_count) %></td>
27
27
  <td><%= invocation_result(invocation, :failed_count) %></td>
@@ -1,3 +1,4 @@
1
1
  $('form#job_invocation_form').replaceWith("<%=j render :partial => 'form' %>");
2
2
  $('#job_invocation_form').find('a[rel="popover"]').popover();
3
3
  $('#job_invocation_form select:not(.without_select2)').select2({ allowClear: true });
4
+ $('div.tooltip').remove();
@@ -0,0 +1,24 @@
1
+ <p>
2
+ <%= _("A job '%{job_name}' has %{status} at %{time}") % {
3
+ :job_name => @job.to_s, :status => @job.status_label, :time => date_time_absolute_value(@job.task.ended_at)
4
+ } %>
5
+ </p>
6
+
7
+ <div class="dashboard">
8
+ <table>
9
+ <tr>
10
+ <td width="18%" class="hosts-rows"><b><%= _("Job result") %></b></td>
11
+ <td width="82%" class="hosts-rows"><%= @job.status_label %></td>
12
+ </tr>
13
+ <tr>
14
+ <td width="18%" class="hosts-rows"><b><%= _("Total hosts") %></b></td>
15
+ <td width="82%" class="hosts-rows"><%= @job.total_hosts_count %></td>
16
+ </tr>
17
+ <tr>
18
+ <td width="18%" class="hosts-rows"><b><%= _("Failed hosts") %></b></td>
19
+ <td width="82%" class="hosts-rows"><%= @job.failed_hosts.count %></td>
20
+ </tr>
21
+ </table>
22
+ </div>
23
+
24
+ <%= link_to 'More details', job_invocation_url(@job) %>
@@ -0,0 +1,9 @@
1
+ <%= _("A job '%{job_name}' has %{status} at %{time}") % {
2
+ :job_name => @job.to_s, :status => @job.status_label, :time => date_time_absolute_value(@job.task.ended_at)
3
+ } %>
4
+
5
+ <%= _('Job result') %>: <%= @job.status_label %>
6
+ <%= _('Total hosts') %>: <%= @job.total_hosts_count %>
7
+ <%= _('Failed hosts') %>: <%= @job.failed_hosts.count %>
8
+
9
+ <%= _('See more details at %s') % job_invocation_url(@job) %>
@@ -18,7 +18,8 @@ end
18
18
 
19
19
  <div id="title_action">
20
20
  <div class="btn-toolbar pull-right">
21
- <%= link_to(_('Back to Job'), job_invocation_path(@template_invocation.job_invocation), :class => 'btn btn-default') %>
21
+ <%= button_group(link_to(_('Back to Job'), job_invocation_path(@template_invocation.job_invocation), :class => 'btn btn-default'),
22
+ (link_to(_('Rerun'), rerun_job_invocation_path(@template_invocation.job_invocation, :host_ids => [ @host.id ]), :class => 'btn btn-default') if authorized_for(:permission => :create_job_invocations))) %>
22
23
  <%= button_group(link_to_function(_('Toggle command'), '$("div.preview").toggle()', :class => 'btn btn-default'),
23
24
  link_to_function(_('Toggle STDERR'), '$("div.line.stderr").toggle()', :class => 'btn btn-default'),
24
25
  link_to_function(_('Toggle STDOUT'), '$("div.line.stdout").toggle()', :class => 'btn btn-default'),
@@ -29,6 +29,7 @@ template_inputs:
29
29
  input_type: user
30
30
  required: false
31
31
  advanced: true
32
+ feature: katello_module_stream_action
32
33
  %>
33
34
 
34
35
  <%
@@ -13,6 +13,8 @@ template_inputs:
13
13
  required: true
14
14
  %>
15
15
 
16
+ PATH="$PATH:/usr/sbin:/sbin"
17
+
16
18
  echo <%= input('action') %> host && sleep 3
17
19
  <%= case input('action')
18
20
  when 'restart'
@@ -10,6 +10,7 @@ template_inputs:
10
10
  description: Additional options to pass to puppet
11
11
  input_type: user
12
12
  required: false
13
+ feature: puppet_run_host
13
14
  %>
14
15
  <% if @host.operatingsystem.family == 'Debian' -%>
15
16
  export PATH=/opt/puppetlabs/bin:$PATH
data/config/routes.rb CHANGED
@@ -45,6 +45,7 @@ Rails.application.routes.draw do
45
45
  get 'cockpit/redirect', to: 'cockpit#redirect'
46
46
  get 'ui_job_wizard/categories', to: 'ui_job_wizard#categories'
47
47
  get 'ui_job_wizard/template/:id', to: 'ui_job_wizard#template'
48
+ get 'ui_job_wizard/resources', to: 'ui_job_wizard#resources'
48
49
 
49
50
  match '/experimental/job_wizard', to: 'react#index', :via => [:get]
50
51
 
@@ -0,0 +1,12 @@
1
+ class AddHostProxyInvocations < ActiveRecord::Migration[6.0]
2
+ def change
3
+ # rubocop:disable Rails/CreateTableWithTimestamps
4
+ create_table :host_proxy_invocations do |t|
5
+ t.references :host, :null => false
6
+ t.references :smart_proxy, :null => false
7
+ end
8
+ # rubocop:enable Rails/CreateTableWithTimestamps
9
+
10
+ add_index :host_proxy_invocations, [:host_id, :smart_proxy_id], unique: true
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ class RexSettingCategoryToDsl < ActiveRecord::Migration[6.0]
2
+ def up
3
+ Setting.where(category: 'Setting::RemoteExecution').update_all(category: 'Setting')
4
+ end
5
+ end
@@ -13,6 +13,20 @@ blueprints = [
13
13
  ],
14
14
  },
15
15
  },
16
+ {
17
+ group: N_('Jobs'),
18
+ name: 'rex_job_failed',
19
+ message: N_("A job '%{subject}' has failed"),
20
+ level: 'error',
21
+ actions:
22
+ {
23
+ links:
24
+ [
25
+ path_method: :job_invocation_path,
26
+ title: N_('Job Details'),
27
+ ],
28
+ },
29
+ },
16
30
  ]
17
31
 
18
32
  blueprints.each { |blueprint| UINotifications::Seed.new(blueprint).configure }
@@ -0,0 +1,24 @@
1
+ N_('Remote execution job')
2
+
3
+ notifications = [
4
+ {
5
+ :name => 'remote_execution_job',
6
+ :description => N_('A notification when a job finishes'),
7
+ :mailer => 'RexJobMailer',
8
+ :method => 'job_finished',
9
+ :subscription_type => 'alert',
10
+ },
11
+ ]
12
+
13
+ notifications.each do |notification|
14
+ if (mail = RexMailNotification.find_by(name: notification[:name]))
15
+ mail.attributes = notification
16
+ mail.save! if mail.changed?
17
+ else
18
+ created_notification = RexMailNotification.create(notification)
19
+ if created_notification.nil? || created_notification.errors.any?
20
+ raise ::Foreman::Exception.new(N_("Unable to create mail notification: %s"),
21
+ format_errors(created_notification))
22
+ end
23
+ end
24
+ end
@@ -16,8 +16,7 @@ Gem::Specification.new do |s|
16
16
  'management functionality with remote management functionality.'
17
17
 
18
18
  s.files = `git ls-files`.split("\n").reject do |file|
19
- file.start_with?('scripts', 'lib/foreman_remote_execution_core') ||
20
- file == 'foreman_remote_execution_core.gemspec'
19
+ file.start_with?('scripts')
21
20
  end
22
21
 
23
22
  s.test_files = `git ls-files test`.split("\n")
@@ -25,7 +24,7 @@ Gem::Specification.new do |s|
25
24
 
26
25
  s.add_dependency 'deface'
27
26
  s.add_dependency 'dynflow', '>= 1.0.2', '< 2.0.0'
28
- s.add_dependency 'foreman-tasks', '>= 4.1.0'
27
+ s.add_dependency 'foreman-tasks', '>= 5.1.0'
29
28
 
30
29
  s.add_development_dependency 'factory_bot_rails', '~> 4.8.0'
31
30
  s.add_development_dependency 'rdoc'
@@ -19,10 +19,6 @@ module ForemanRemoteExecution
19
19
  end
20
20
  assets_to_precompile += %w(foreman_remote_execution/foreman_remote_execution.css)
21
21
 
22
- initializer 'foreman_remote_execution.load_default_settings', :before => :load_config_initializers do
23
- require_dependency File.expand_path('../../../app/models/setting/remote_execution.rb', __FILE__) if (Setting.table_exists? rescue(false))
24
- end
25
-
26
22
  # Add any db migrations
27
23
  initializer 'foreman_remote_execution.load_app_instance_data' do |app|
28
24
  ForemanRemoteExecution::Engine.paths['db/migrate'].existent.each do |path|
@@ -51,7 +47,7 @@ module ForemanRemoteExecution
51
47
 
52
48
  initializer 'foreman_remote_execution.register_plugin', before: :finisher_hook do |_app|
53
49
  Foreman::Plugin.register :foreman_remote_execution do
54
- requires_foreman '>= 2.2'
50
+ requires_foreman '>= 3.1'
55
51
  register_global_js_file 'global'
56
52
 
57
53
  apipie_documented_controllers ["#{ForemanRemoteExecution::Engine.root}/app/controllers/api/v2/*.rb"]
@@ -61,13 +57,114 @@ module ForemanRemoteExecution
61
57
  automatic_assets(false)
62
58
  precompile_assets(*assets_to_precompile)
63
59
 
60
+ # Add settings to a Remote Execution category
61
+ settings do
62
+ category :remote_execution, N_('Remote Execution') do
63
+ setting 'remote_execution_fallback_proxy',
64
+ type: :boolean,
65
+ description: N_('Search the host for any proxy with Remote Execution, useful when the host has no subnet or the subnet does not have an execution proxy'),
66
+ default: false,
67
+ full_name: N_('Fallback to Any Proxy')
68
+ setting 'remote_execution_global_proxy',
69
+ type: :boolean,
70
+ description: N_('Search for remote execution proxy outside of the proxies assigned to the host. The search will be limited to the host\'s organization and location.'),
71
+ default: true,
72
+ full_name: N_('Enable Global Proxy')
73
+ setting 'remote_execution_ssh_user',
74
+ type: :string,
75
+ description: N_('Default user to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_user.'),
76
+ default: 'root',
77
+ full_name: N_('SSH User')
78
+ setting 'remote_execution_effective_user',
79
+ type: :string,
80
+ description: N_('Default user to use for executing the script. If the user differs from the SSH user, su or sudo is used to switch the user.'),
81
+ default: 'root',
82
+ full_name: N_('Efffective User')
83
+ setting 'remote_execution_effective_user_method',
84
+ type: :string,
85
+ description: N_('What command should be used to switch to the effective user. One of %s') % SSHExecutionProvider::EFFECTIVE_USER_METHODS.inspect,
86
+ default: 'sudo',
87
+ full_name: N_('Effective User Method'),
88
+ collection: proc { Hash[SSHExecutionProvider::EFFECTIVE_USER_METHODS.map { |method| [method, method] }] }
89
+ setting 'remote_execution_effective_user_password',
90
+ type: :string,
91
+ description: N_('Effective user password'),
92
+ default: '',
93
+ full_name: N_('Effective user password'),
94
+ encrypted: true
95
+ setting 'remote_execution_sync_templates',
96
+ type: :boolean,
97
+ description: N_('Whether we should sync templates from disk when running db:seed.'),
98
+ default: true,
99
+ full_name: N_('Sync Job Templates')
100
+ setting 'remote_execution_ssh_port',
101
+ type: :integer,
102
+ description: N_('Port to use for SSH communication. Default port 22. You may override per host by setting a parameter called remote_execution_ssh_port.'),
103
+ default: 22,
104
+ full_name: N_('SSH Port')
105
+ setting 'remote_execution_connect_by_ip',
106
+ type: :boolean,
107
+ description: N_('Should the ip addresses on host interfaces be preferred over the fqdn? '\
108
+ 'It is useful when DNS not resolving the fqdns properly. You may override this per host by setting a parameter called remote_execution_connect_by_ip. '\
109
+ 'For dual-stacked hosts you should consider the remote_execution_connect_by_ip_prefer_ipv6 setting'),
110
+ default: false,
111
+ full_name: N_('Connect by IP')
112
+ setting 'remote_execution_connect_by_ip_prefer_ipv6',
113
+ type: :boolean,
114
+ description: N_('When connecting using ip address, should the IPv6 addresses be preferred? '\
115
+ 'If no IPv6 address is set, it falls back to IPv4 automatically. You may override this per host by setting a parameter called remote_execution_connect_by_ip_prefer_ipv6. '\
116
+ 'By default and for compatibility, IPv4 will be preferred over IPv6 by default'),
117
+ default: false,
118
+ full_name: N_('Prefer IPv6 over IPv4')
119
+ setting 'remote_execution_ssh_password',
120
+ type: :string,
121
+ description: N_('Default password to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_password'),
122
+ default: nil,
123
+ full_name: N_('Default SSH password'),
124
+ encrypted: true
125
+ setting 'remote_execution_ssh_key_passphrase',
126
+ type: :string,
127
+ description: N_('Default key passphrase to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_key_passphrase'),
128
+ default: nil,
129
+ full_name: N_('Default SSH key passphrase'),
130
+ encrypted: true
131
+ setting 'remote_execution_workers_pool_size',
132
+ type: :integer,
133
+ description: N_('Amount of workers in the pool to handle the execution of the remote execution jobs. Restart of the dynflowd/foreman-tasks service is required.'),
134
+ default: 5,
135
+ full_name: N_('Workers pool size')
136
+ setting 'remote_execution_cleanup_working_dirs',
137
+ type: :boolean,
138
+ description: N_('When enabled, working directories will be removed after task completion. You may override this per host by setting a parameter called remote_execution_cleanup_working_dirs.'),
139
+ default: true,
140
+ full_name: N_('Cleanup working directories')
141
+ setting 'remote_execution_cockpit_url',
142
+ type: :string,
143
+ description: N_('Where to find the Cockpit instance for the Web Console button. By default, no button is shown.'),
144
+ default: nil,
145
+ full_name: N_('Cockpit URL')
146
+ setting 'remote_execution_form_job_template',
147
+ type: :string,
148
+ description: N_('Choose a job template that is pre-selected in job invocation form'),
149
+ default: 'Run Command - SSH Default',
150
+ full_name: N_('Form Job Template'),
151
+ collection: proc { Hash[JobTemplate.unscoped.map { |template| [template.name, template.name] }] }
152
+ setting 'remote_execution_job_invocation_report_template',
153
+ type: :string,
154
+ description: N_('Select a report template used for generating a report for a particular remote execution job'),
155
+ default: 'Jobs - Invocation report template',
156
+ full_name: N_('Job Invocation Report Template'),
157
+ collection: proc { ForemanRemoteExecution.job_invocation_report_templates_select }
158
+ end
159
+ end
160
+
64
161
  # Add permissions
65
162
  security_block :foreman_remote_execution do
66
163
  permission :view_job_templates, { :job_templates => [:index, :show, :revision, :auto_complete_search, :auto_complete_job_category, :preview, :export],
67
164
  :'api/v2/job_templates' => [:index, :show, :revision, :export],
68
165
  :'api/v2/template_inputs' => [:index, :show],
69
166
  :'api/v2/foreign_input_sets' => [:index, :show],
70
- :ui_job_wizard => [:categories, :template]}, :resource_type => 'JobTemplate'
167
+ :ui_job_wizard => [:categories, :template, :resources]}, :resource_type => 'JobTemplate'
71
168
  permission :create_job_templates, { :job_templates => [:new, :create, :clone_template, :import],
72
169
  :'api/v2/job_templates' => [:create, :clone, :import] }, :resource_type => 'JobTemplate'
73
170
  permission :edit_job_templates, { :job_templates => [:edit, :update],
@@ -86,6 +183,7 @@ module ForemanRemoteExecution
86
183
  permission :view_template_invocations, { :template_invocations => [:show],
87
184
  'api/v2/template_invocations' => [:template_invocations] }, :resource_type => 'TemplateInvocation'
88
185
  permission :create_template_invocations, {}, :resource_type => 'TemplateInvocation'
186
+ permission :execute_jobs_on_infrastructure_hosts, {}, :resource_type => 'JobInvocation'
89
187
  permission :cancel_job_invocations, { :job_invocations => [:cancel], 'api/v2/job_invocations' => [:cancel] }, :resource_type => 'JobInvocation'
90
188
  # this permissions grants user to get auto completion hints when setting up filters
91
189
  permission :filter_autocompletion_for_template_invocation, { :template_invocations => [ :auto_complete_search, :index ] },
@@ -146,12 +244,18 @@ module ForemanRemoteExecution
146
244
  register_custom_status HostStatus::ExecutionStatus
147
245
  # add dashboard widget
148
246
  # widget 'foreman_remote_execution_widget', name: N_('Foreman plugin template widget'), sizex: 4, sizey: 1
247
+ widget 'dashboard/latest-jobs', :name => N_('Latest Jobs'), :sizex => 6, :sizey => 1
149
248
 
150
249
  parameter_filter Subnet, :remote_execution_proxies, :remote_execution_proxy_ids => []
151
250
  parameter_filter Nic::Interface do |ctx|
152
251
  ctx.permit :execution
153
252
  end
154
253
 
254
+ register_graphql_query_field :job_invocations, '::Types::JobInvocation', :collection_field
255
+ register_graphql_query_field :job_invocation, '::Types::JobInvocation', :record_field
256
+
257
+ register_graphql_mutation_field :create_job_invocation, ::Mutations::JobInvocations::Create
258
+
155
259
  extend_template_helpers ForemanRemoteExecution::RendererMethods
156
260
 
157
261
  extend_rabl_template 'api/v2/smart_proxies/main', 'api/v2/smart_proxies/pubkey'
@@ -201,12 +305,10 @@ module ForemanRemoteExecution
201
305
 
202
306
  Host::Managed.prepend ForemanRemoteExecution::HostExtensions
203
307
  Host::Managed.include ForemanTasks::Concerns::HostActionSubject
204
- Host::Managed.include ForemanRemoteExecution::Orchestration::SSH
205
308
 
206
309
  (Nic::Base.descendants + [Nic::Base]).each do |klass|
207
310
  klass.send(:include, ForemanRemoteExecution::NicExtensions)
208
311
  end
209
- Nic::Managed.include ForemanRemoteExecution::Orchestration::SSH
210
312
 
211
313
  Bookmark.include ForemanRemoteExecution::BookmarkExtensions
212
314
  HostsHelper.prepend ForemanRemoteExecution::HostsHelperExtensions
@@ -239,6 +341,10 @@ module ForemanRemoteExecution
239
341
  end
240
342
  end
241
343
 
344
+ def self.job_invocation_report_templates_select
345
+ Hash[ReportTemplate.unscoped.joins(:template_inputs).where(template_inputs: TemplateInput.where(name: 'job_id')).map { |template| [template.name, template.name] }]
346
+ end
347
+
242
348
  def self.register_rex_feature
243
349
  RemoteExecutionFeature.register(
244
350
  :puppet_run_host,
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '4.6.0'.freeze
2
+ VERSION = '5.0.1'.freeze
3
3
  end