foreman_remote_execution 4.7.0 → 5.0.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +1 -0
  3. data/app/controllers/api/v2/job_invocations_controller.rb +16 -1
  4. data/app/controllers/ui_job_wizard_controller.rb +16 -4
  5. data/app/graphql/mutations/job_invocations/create.rb +43 -0
  6. data/app/graphql/types/job_invocation_input.rb +13 -0
  7. data/app/graphql/types/recurrence_input.rb +8 -0
  8. data/app/graphql/types/scheduling_input.rb +6 -0
  9. data/app/graphql/types/targeting_enum.rb +7 -0
  10. data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +20 -9
  11. data/app/helpers/remote_execution_helper.rb +1 -1
  12. data/app/lib/actions/remote_execution/run_host_job.rb +6 -1
  13. data/app/lib/actions/remote_execution/run_hosts_job.rb +57 -3
  14. data/app/mailers/rex_job_mailer.rb +15 -0
  15. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +12 -0
  16. data/app/models/job_invocation.rb +4 -0
  17. data/app/models/job_invocation_composer.rb +21 -13
  18. data/app/models/remote_execution_provider.rb +18 -2
  19. data/app/models/rex_mail_notification.rb +13 -0
  20. data/app/models/targeting.rb +3 -3
  21. data/app/services/ui_notifications/remote_execution_jobs/base_job_finish.rb +2 -1
  22. data/app/views/dashboard/_latest-jobs.html.erb +21 -0
  23. data/app/views/job_invocations/_preview_hosts_list.html.erb +1 -1
  24. data/app/views/job_invocations/refresh.js.erb +1 -0
  25. data/app/views/rex_job_mailer/job_finished.html.erb +24 -0
  26. data/app/views/rex_job_mailer/job_finished.text.erb +9 -0
  27. data/app/views/template_invocations/show.html.erb +3 -2
  28. data/config/routes.rb +1 -0
  29. data/db/migrate/20210816100932_rex_setting_category_to_dsl.rb +5 -0
  30. data/db/seeds.d/50-notification_blueprints.rb +14 -0
  31. data/db/seeds.d/95-mail_notifications.rb +24 -0
  32. data/foreman_remote_execution.gemspec +1 -1
  33. data/lib/foreman_remote_execution/engine.rb +116 -7
  34. data/lib/foreman_remote_execution/version.rb +1 -1
  35. data/package.json +9 -7
  36. data/test/functional/api/v2/job_invocations_controller_test.rb +20 -0
  37. data/test/functional/cockpit_controller_test.rb +0 -1
  38. data/test/graphql/mutations/job_invocations/create.rb +58 -0
  39. data/test/helpers/remote_execution_helper_test.rb +0 -1
  40. data/test/unit/actions/run_host_job_test.rb +21 -0
  41. data/test/unit/actions/run_hosts_job_test.rb +99 -4
  42. data/test/unit/concerns/host_extensions_test.rb +36 -3
  43. data/test/unit/job_invocation_composer_test.rb +3 -5
  44. data/test/unit/job_invocation_report_template_test.rb +16 -13
  45. data/test/unit/job_template_effective_user_test.rb +0 -4
  46. data/test/unit/remote_execution_provider_test.rb +46 -4
  47. data/test/unit/targeting_test.rb +68 -1
  48. data/webpack/JobWizard/JobWizard.js +142 -28
  49. data/webpack/JobWizard/JobWizard.scss +86 -33
  50. data/webpack/JobWizard/JobWizardConstants.js +44 -0
  51. data/webpack/JobWizard/JobWizardSelectors.js +32 -0
  52. data/webpack/JobWizard/__tests__/fixtures.js +89 -6
  53. data/webpack/JobWizard/__tests__/integration.test.js +29 -22
  54. data/webpack/JobWizard/__tests__/validation.test.js +141 -0
  55. data/webpack/JobWizard/autofill.js +38 -0
  56. data/webpack/JobWizard/index.js +7 -0
  57. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +23 -9
  58. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +32 -9
  59. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +48 -1
  60. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +242 -23
  61. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +82 -0
  62. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +5 -2
  63. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +3 -2
  64. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -2
  65. data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +62 -0
  66. data/webpack/JobWizard/steps/HostsAndInputs/HostSearch.js +54 -0
  67. data/webpack/JobWizard/steps/HostsAndInputs/SelectAPI.js +33 -0
  68. data/webpack/JobWizard/steps/HostsAndInputs/SelectGQL.js +52 -0
  69. data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +100 -0
  70. data/webpack/JobWizard/steps/HostsAndInputs/TemplateInputs.js +23 -0
  71. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +151 -0
  72. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +53 -0
  73. data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +18 -0
  74. data/webpack/JobWizard/steps/HostsAndInputs/hostgroups.gql +8 -0
  75. data/webpack/JobWizard/steps/HostsAndInputs/hosts.gql +8 -0
  76. data/webpack/JobWizard/steps/HostsAndInputs/index.js +214 -0
  77. data/webpack/JobWizard/steps/ReviewDetails/index.js +193 -0
  78. data/webpack/JobWizard/steps/Schedule/PurposeField.js +31 -0
  79. data/webpack/JobWizard/steps/Schedule/QueryType.js +46 -43
  80. data/webpack/JobWizard/steps/Schedule/RepeatCron.js +53 -0
  81. data/webpack/JobWizard/steps/Schedule/RepeatDaily.js +37 -0
  82. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +54 -0
  83. data/webpack/JobWizard/steps/Schedule/RepeatMonth.js +46 -0
  84. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +95 -31
  85. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +70 -0
  86. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +24 -21
  87. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +78 -23
  88. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +402 -0
  89. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +20 -10
  90. data/webpack/JobWizard/steps/Schedule/index.js +166 -29
  91. data/webpack/JobWizard/steps/form/DateTimePicker.js +126 -0
  92. data/webpack/JobWizard/steps/form/FormHelpers.js +4 -0
  93. data/webpack/JobWizard/steps/form/Formatter.js +49 -17
  94. data/webpack/JobWizard/steps/form/NumberInput.js +5 -2
  95. data/webpack/JobWizard/steps/form/ResourceSelect.js +29 -0
  96. data/webpack/JobWizard/steps/form/SearchSelect.js +121 -0
  97. data/webpack/JobWizard/steps/form/SelectField.js +14 -3
  98. data/webpack/JobWizard/steps/form/WizardTitle.js +14 -0
  99. data/webpack/JobWizard/steps/form/__tests__/SelectSearch.test.js +33 -0
  100. data/webpack/JobWizard/submit.js +120 -0
  101. data/webpack/JobWizard/validation.js +53 -0
  102. data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext/index.js +2 -0
  103. data/webpack/__mocks__/foremanReact/common/I18n.js +2 -0
  104. data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteActions.js +1 -0
  105. data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteConstants.js +1 -0
  106. data/webpack/__mocks__/foremanReact/routes/RouterSelector.js +1 -0
  107. data/webpack/helpers.js +1 -0
  108. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +2 -1
  109. metadata +53 -7
  110. data/app/models/setting/remote_execution.rb +0 -88
  111. data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +0 -23
  112. data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +0 -76
@@ -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'),
@@ -27,7 +28,7 @@ end
27
28
  </div>
28
29
  </div>
29
30
  <% if @host %>
30
- <h3><%= _('Target: ') %><%= link_to(@host.name, host_path(@host)) %></h3>
31
+ <h3><%= _('Target: ') %><%= link_to(@host.name, current_host_details_path(@host)) %></h3>
31
32
 
32
33
  <div class="preview hidden">
33
34
  <%= preview_box(@template_invocation, @host) %>
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,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
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
24
24
 
25
25
  s.add_dependency 'deface'
26
26
  s.add_dependency 'dynflow', '>= 1.0.2', '< 2.0.0'
27
- s.add_dependency 'foreman-tasks', '>= 5.0.0'
27
+ s.add_dependency 'foreman-tasks', '>= 5.1.0'
28
28
 
29
29
  s.add_development_dependency 'factory_bot_rails', '~> 4.8.0'
30
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 ] },
@@ -116,7 +214,11 @@ module ForemanRemoteExecution
116
214
  role 'Remote Execution User', USER_PERMISSIONS, 'Role with permissions to run remote execution jobs against hosts'
117
215
  role 'Remote Execution Manager', MANAGER_PERMISSIONS, 'Role with permissions to manage job templates, remote execution features, cancel jobs and view audit logs'
118
216
 
119
- add_all_permissions_to_default_roles
217
+ add_all_permissions_to_default_roles(except: [:execute_jobs_on_infrastructure_hosts])
218
+ add_permissions_to_default_roles({
219
+ Role::MANAGER => [:execute_jobs_on_infrastructure_hosts],
220
+ Role::SITE_MANAGER => USER_PERMISSIONS + [:execute_jobs_on_infrastructure_hosts],
221
+ })
120
222
 
121
223
  # add menu entry
122
224
  menu :top_menu, :job_templates,
@@ -146,6 +248,7 @@ module ForemanRemoteExecution
146
248
  register_custom_status HostStatus::ExecutionStatus
147
249
  # add dashboard widget
148
250
  # widget 'foreman_remote_execution_widget', name: N_('Foreman plugin template widget'), sizex: 4, sizey: 1
251
+ widget 'dashboard/latest-jobs', :name => N_('Latest Jobs'), :sizex => 6, :sizey => 1
149
252
 
150
253
  parameter_filter Subnet, :remote_execution_proxies, :remote_execution_proxy_ids => []
151
254
  parameter_filter Nic::Interface do |ctx|
@@ -155,6 +258,8 @@ module ForemanRemoteExecution
155
258
  register_graphql_query_field :job_invocations, '::Types::JobInvocation', :collection_field
156
259
  register_graphql_query_field :job_invocation, '::Types::JobInvocation', :record_field
157
260
 
261
+ register_graphql_mutation_field :create_job_invocation, ::Mutations::JobInvocations::Create
262
+
158
263
  extend_template_helpers ForemanRemoteExecution::RendererMethods
159
264
 
160
265
  extend_rabl_template 'api/v2/smart_proxies/main', 'api/v2/smart_proxies/pubkey'
@@ -240,6 +345,10 @@ module ForemanRemoteExecution
240
345
  end
241
346
  end
242
347
 
348
+ def self.job_invocation_report_templates_select
349
+ Hash[ReportTemplate.unscoped.joins(:template_inputs).where(template_inputs: TemplateInput.where(name: 'job_id')).map { |template| [template.name, template.name] }]
350
+ end
351
+
243
352
  def self.register_rex_feature
244
353
  RemoteExecutionFeature.register(
245
354
  :puppet_run_host,
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '4.7.0'.freeze
2
+ VERSION = '5.0.2'.freeze
3
3
  end
data/package.json CHANGED
@@ -21,18 +21,20 @@
21
21
  },
22
22
  "devDependencies": {
23
23
  "@babel/core": "^7.7.0",
24
- "@theforeman/builder": "^4.14.0",
25
- "@theforeman/eslint-plugin-foreman": "^4.14.0",
26
- "@theforeman/stories": "^4.14.0",
27
- "@theforeman/test": "^4.14.0",
28
- "@theforeman/vendor-dev": "^4.14.0",
24
+ "@theforeman/builder": "^8.16.0",
25
+ "@theforeman/eslint-plugin-foreman": "^8.16.0",
26
+ "@theforeman/stories": "^8.16.0",
27
+ "@theforeman/test": "^8.16.0",
28
+ "@theforeman/vendor-dev": "^8.16.0",
29
29
  "babel-eslint": "^10.0.0",
30
30
  "eslint": "^6.8.0",
31
31
  "prettier": "^1.19.1",
32
32
  "@patternfly/react-catalog-view-extension": "^4.8.126",
33
- "redux-mock-store": "^1.2.2"
33
+ "redux-mock-store": "^1.2.2",
34
+ "graphql-tag": "^2.11.0",
35
+ "graphql": "^15.5.0"
34
36
  },
35
37
  "peerDependencies": {
36
- "@theforeman/vendor": "^8.3.0"
38
+ "@theforeman/vendor": "^8.16.0"
37
39
  }
38
40
  }
@@ -90,6 +90,16 @@ module Api
90
90
  assert_response :success
91
91
  end
92
92
 
93
+ test 'should create with a scheduled recurrence' do
94
+ @attrs[:scheduling] = { start_at: (Time.now + 1.hour) }
95
+ @attrs[:recurrence] = { cron_line: '5 * * * *' }
96
+ post :create, params: { job_invocation: @attrs }
97
+ invocation = ActiveSupport::JSON.decode(@response.body)
98
+ assert_equal 'recurring', invocation['mode']
99
+ assert invocation['start_at']
100
+ assert_response :success
101
+ end
102
+
93
103
  context 'with_feature' do
94
104
  setup do
95
105
  @feature = FactoryBot.create(:remote_execution_feature,
@@ -181,6 +191,11 @@ module Api
181
191
  host_id: FactoryBot.create(:host).id }
182
192
  assert_response :missing
183
193
  end
194
+
195
+ test 'should not break when taxonomy parameters are provided' do
196
+ get :output, params: { :job_invocation_id => @invocation.id, :host_id => host.id, :organization_id => host.organization_id, :location_id => host.location_id }
197
+ assert_response :success
198
+ end
184
199
  end
185
200
 
186
201
  describe '#outputs' do
@@ -233,6 +248,11 @@ module Api
233
248
  assert_response :success
234
249
  end
235
250
 
251
+ test 'should not break when taxonomy parameters are provided' do
252
+ get :raw_output, params: { :job_invocation_id => @invocation.id, :host_id => host.id, :organization_id => host.organization_id, :location_id => host.location_id }
253
+ assert_response :success
254
+ end
255
+
236
256
  test 'should provide raw output for delayed task' do
237
257
  start_time = Time.now
238
258
  JobInvocation.any_instance
@@ -2,7 +2,6 @@ require 'test_plugin_helper'
2
2
 
3
3
  class CockpitControllerTest < ActionController::TestCase
4
4
  def setup
5
- Setting::RemoteExecution.load_defaults
6
5
  as_admin do
7
6
  @host = FactoryBot.create(:host)
8
7
  end
@@ -0,0 +1,58 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module Mutations
4
+ module JobInvocations
5
+ class CreateMutationTest < ActiveSupport::TestCase
6
+ let(:host) { FactoryBot.create(:host) }
7
+ let(:job_template) { FactoryBot.create(:job_template, :with_input) }
8
+ let(:cron_line) { '5 * * * *' }
9
+ let(:purpose) { 'test' }
10
+ let(:variables) do
11
+ {
12
+ jobInvocation: {
13
+ hostIds: [host.id],
14
+ jobTemplateId: job_template.id,
15
+ targetingType: 'static_query',
16
+ inputs: { job_template.template_inputs.first.name => "bar" },
17
+ recurrence: {
18
+ cronLine: cron_line,
19
+ purpose: purpose,
20
+ },
21
+ },
22
+ }
23
+ end
24
+
25
+ let(:query) do
26
+ <<-GRAPHQL
27
+ mutation CreateJobInvocation($jobInvocation: JobInvocationInput!) {
28
+ createJobInvocation(input: { jobInvocation: $jobInvocation }) {
29
+ jobInvocation {
30
+ id
31
+ description
32
+ recurringLogic {
33
+ cronLine
34
+ purpose
35
+ }
36
+ }
37
+ }
38
+ }
39
+ GRAPHQL
40
+ end
41
+
42
+ context 'with admin user' do
43
+ let(:user) { FactoryBot.create(:user, :admin) }
44
+ let(:context) { { current_user: user } }
45
+
46
+ test 'create a job invocation' do
47
+ assert_difference('JobInvocation.count', +1) do
48
+ result = ForemanGraphqlSchema.execute(query, variables: variables, context: context)
49
+ assert_empty result['errors']
50
+ assert_empty result['data']['createJobInvocation']['jobInvocation']['errors']
51
+ assert_equal cron_line, result['data']['createJobInvocation']['jobInvocation']['recurringLogic']['cronLine']
52
+ assert_equal purpose, result['data']['createJobInvocation']['jobInvocation']['recurringLogic']['purpose']
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -26,7 +26,6 @@ class RemoteExecutionHelperTest < ActionView::TestCase
26
26
 
27
27
  describe 'test correct setting' do
28
28
  it 'should found correct template from setting' do
29
- Setting::RemoteExecution.load_defaults
30
29
  template_name = 'Job Invocation Report Template'
31
30
  setting_key = 'remote_execution_job_invocation_report_template'
32
31
  template = FactoryBot.create(:report_template, name: template_name)
@@ -36,6 +36,27 @@ module ForemanRemoteExecution
36
36
  end
37
37
  end
38
38
 
39
+ describe '#verify_permission' do
40
+ let(:job_invocation) { FactoryBot.create(:job_invocation, :with_task) }
41
+ let(:template_invocation) { job_invocation.template_invocations.first }
42
+
43
+ before { job_invocation }
44
+
45
+ it 'raises an exception when run against an infrastructure host' do
46
+ template_invocation.host = FactoryBot.create(:host, :with_infrastructure_facet)
47
+
48
+ setup_user('view', 'hosts')
49
+ setup_user('view', 'job_templates')
50
+ setup_user('create', 'template_invocations')
51
+
52
+ action = Actions::RemoteExecution::RunHostJob.allocate
53
+ exception = assert_raises do
54
+ action.send(:verify_permissions, template_invocation.host, template_invocation)
55
+ end
56
+ _(exception.message).must_include "infrastructure host #{template_invocation.host.name}"
57
+ end
58
+ end
59
+
39
60
  describe '#finalize' do
40
61
  let(:host) { FactoryBot.create(:host, :with_execution) }
41
62
 
@@ -5,6 +5,16 @@ module ForemanRemoteExecution
5
5
  class RunHostsJobTest < ActiveSupport::TestCase
6
6
  include Dynflow::Testing
7
7
 
8
+ # Adding run_step_id wich is needed in RunHostsJob as a quick fix
9
+ # it will be added to dynflow in the future see https://github.com/Dynflow/dynflow/pull/391
10
+ # rubocop:disable Style/ClassAndModuleChildren
11
+ class Dynflow::Testing::DummyPlannedAction
12
+ def run_step_id
13
+ Dynflow::Testing.get_id
14
+ end
15
+ end
16
+ # rubocop:enable Style/ClassAndModuleChildren
17
+
8
18
  let(:host) { FactoryBot.create(:host, :with_execution) }
9
19
  let(:proxy) { host.remote_execution_proxies('SSH')[:subnet].first }
10
20
  let(:targeting) { FactoryBot.create(:targeting, :search_query => "name = #{host.name}", :user => User.current) }
@@ -94,6 +104,22 @@ module ForemanRemoteExecution
94
104
  planned # To make the expectations happy
95
105
  end
96
106
 
107
+ describe '#proxy_batch_size' do
108
+ it 'defaults to Setting[foreman_tasks_proxy_batch_size]' do
109
+ Setting.expects(:[]).with('foreman_tasks_proxy_batch_size').returns(14)
110
+ planned
111
+ _(planned.proxy_batch_size).must_equal 14
112
+ end
113
+
114
+ it 'gets the provider value' do
115
+ provider = mock('provider')
116
+ provider.expects(:proxy_batch_size).returns(15)
117
+ JobTemplate.any_instance.expects(:provider).returns(provider)
118
+
119
+ _(planned.proxy_batch_size).must_equal 15
120
+ end
121
+ end
122
+
97
123
  describe 'concurrency control' do
98
124
  let(:level) { 5 }
99
125
  let(:span) { 60 }
@@ -127,10 +153,79 @@ module ForemanRemoteExecution
127
153
  end
128
154
 
129
155
  describe 'notifications' do
130
- it 'creates notification on sucess run' do
131
- FactoryBot.create(:notification_blueprint, :name => 'rex_job_succeeded')
132
- assert_difference 'NotificationRecipient.where(:user_id => targeting.user.id).count' do
133
- finalize_action planned
156
+ it 'creates drawer notification on succeess' do
157
+ blueprint = planned.job_invocation.build_notification
158
+ blueprint.expects(:deliver!)
159
+ planned.job_invocation.expects(:build_notification).returns(blueprint)
160
+ planned.notify_on_success(nil)
161
+ end
162
+
163
+ it 'creates drawer notification on failure' do
164
+ blueprint = planned.job_invocation.build_notification
165
+ blueprint.expects(:deliver!)
166
+ planned.job_invocation.expects(:build_notification).returns(blueprint)
167
+ planned.notify_on_failure(nil)
168
+ end
169
+
170
+ describe 'ignoring drawer notification' do
171
+ before do
172
+ blueprint = planned.job_invocation.build_notification
173
+ blueprint.expects(:deliver!)
174
+ planned.job_invocation.expects(:build_notification).returns(blueprint)
175
+ end
176
+
177
+ let(:mail) do
178
+ object = mock
179
+ object.stubs(:deliver_now)
180
+ object
181
+ end
182
+
183
+ describe 'for user subscribed to all' do
184
+ before do
185
+ planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::ALL_JOBS))
186
+ end
187
+
188
+ it 'sends the mail notification on success' do
189
+ RexJobMailer.expects(:job_finished).returns(mail)
190
+ planned.notify_on_success(nil)
191
+ end
192
+
193
+ it 'sends the mail notification on failure' do
194
+ RexJobMailer.expects(:job_finished).returns(mail)
195
+ planned.notify_on_failure(nil)
196
+ end
197
+ end
198
+
199
+ describe 'for user subscribed to failures' do
200
+ before do
201
+ planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::FAILED_JOBS))
202
+ end
203
+
204
+ it 'it does not send the mail notification on success' do
205
+ RexJobMailer.expects(:job_finished).never
206
+ planned.notify_on_success(nil)
207
+ end
208
+
209
+ it 'sends the mail notification on failure' do
210
+ RexJobMailer.expects(:job_finished).returns(mail)
211
+ planned.notify_on_failure(nil)
212
+ end
213
+ end
214
+
215
+ describe 'for user subscribed to successful jobs' do
216
+ before do
217
+ planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::SUCCEEDED_JOBS))
218
+ end
219
+
220
+ it 'sends the mail notification on success' do
221
+ RexJobMailer.expects(:job_finished).returns(mail)
222
+ planned.notify_on_success(nil)
223
+ end
224
+
225
+ it 'does not send the mail notification on failure' do
226
+ RexJobMailer.expects(:job_finished).never
227
+ planned.notify_on_failure(nil)
228
+ end
134
229
  end
135
230
  end
136
231
  end