foreman_remote_execution 4.7.0 → 5.0.2

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