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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +1 -0
- data/app/controllers/api/v2/job_invocations_controller.rb +16 -1
- data/app/controllers/ui_job_wizard_controller.rb +16 -4
- data/app/graphql/mutations/job_invocations/create.rb +43 -0
- data/app/graphql/types/job_invocation_input.rb +13 -0
- data/app/graphql/types/recurrence_input.rb +8 -0
- data/app/graphql/types/scheduling_input.rb +6 -0
- data/app/graphql/types/targeting_enum.rb +7 -0
- data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +20 -9
- data/app/helpers/remote_execution_helper.rb +1 -1
- data/app/lib/actions/remote_execution/run_host_job.rb +6 -1
- data/app/lib/actions/remote_execution/run_hosts_job.rb +57 -3
- data/app/mailers/rex_job_mailer.rb +15 -0
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +12 -0
- data/app/models/job_invocation.rb +4 -0
- data/app/models/job_invocation_composer.rb +21 -13
- data/app/models/remote_execution_provider.rb +18 -2
- data/app/models/rex_mail_notification.rb +13 -0
- data/app/models/targeting.rb +3 -3
- data/app/services/ui_notifications/remote_execution_jobs/base_job_finish.rb +2 -1
- data/app/views/dashboard/_latest-jobs.html.erb +21 -0
- data/app/views/job_invocations/_preview_hosts_list.html.erb +1 -1
- data/app/views/job_invocations/refresh.js.erb +1 -0
- data/app/views/rex_job_mailer/job_finished.html.erb +24 -0
- data/app/views/rex_job_mailer/job_finished.text.erb +9 -0
- data/app/views/template_invocations/show.html.erb +3 -2
- data/config/routes.rb +1 -0
- data/db/migrate/20210816100932_rex_setting_category_to_dsl.rb +5 -0
- data/db/seeds.d/50-notification_blueprints.rb +14 -0
- data/db/seeds.d/95-mail_notifications.rb +24 -0
- data/foreman_remote_execution.gemspec +1 -1
- data/lib/foreman_remote_execution/engine.rb +116 -7
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/package.json +9 -7
- data/test/functional/api/v2/job_invocations_controller_test.rb +20 -0
- data/test/functional/cockpit_controller_test.rb +0 -1
- data/test/graphql/mutations/job_invocations/create.rb +58 -0
- data/test/helpers/remote_execution_helper_test.rb +0 -1
- data/test/unit/actions/run_host_job_test.rb +21 -0
- data/test/unit/actions/run_hosts_job_test.rb +99 -4
- data/test/unit/concerns/host_extensions_test.rb +36 -3
- data/test/unit/job_invocation_composer_test.rb +3 -5
- data/test/unit/job_invocation_report_template_test.rb +16 -13
- data/test/unit/job_template_effective_user_test.rb +0 -4
- data/test/unit/remote_execution_provider_test.rb +46 -4
- data/test/unit/targeting_test.rb +68 -1
- data/webpack/JobWizard/JobWizard.js +142 -28
- data/webpack/JobWizard/JobWizard.scss +86 -33
- data/webpack/JobWizard/JobWizardConstants.js +44 -0
- data/webpack/JobWizard/JobWizardSelectors.js +32 -0
- data/webpack/JobWizard/__tests__/fixtures.js +89 -6
- data/webpack/JobWizard/__tests__/integration.test.js +29 -22
- data/webpack/JobWizard/__tests__/validation.test.js +141 -0
- data/webpack/JobWizard/autofill.js +38 -0
- data/webpack/JobWizard/index.js +7 -0
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +23 -9
- data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +32 -9
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +48 -1
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +242 -23
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +82 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +5 -2
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +3 -2
- data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -2
- data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +62 -0
- data/webpack/JobWizard/steps/HostsAndInputs/HostSearch.js +54 -0
- data/webpack/JobWizard/steps/HostsAndInputs/SelectAPI.js +33 -0
- data/webpack/JobWizard/steps/HostsAndInputs/SelectGQL.js +52 -0
- data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +100 -0
- data/webpack/JobWizard/steps/HostsAndInputs/TemplateInputs.js +23 -0
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +151 -0
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +53 -0
- data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +18 -0
- data/webpack/JobWizard/steps/HostsAndInputs/hostgroups.gql +8 -0
- data/webpack/JobWizard/steps/HostsAndInputs/hosts.gql +8 -0
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +214 -0
- data/webpack/JobWizard/steps/ReviewDetails/index.js +193 -0
- data/webpack/JobWizard/steps/Schedule/PurposeField.js +31 -0
- data/webpack/JobWizard/steps/Schedule/QueryType.js +46 -43
- data/webpack/JobWizard/steps/Schedule/RepeatCron.js +53 -0
- data/webpack/JobWizard/steps/Schedule/RepeatDaily.js +37 -0
- data/webpack/JobWizard/steps/Schedule/RepeatHour.js +54 -0
- data/webpack/JobWizard/steps/Schedule/RepeatMonth.js +46 -0
- data/webpack/JobWizard/steps/Schedule/RepeatOn.js +95 -31
- data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +70 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleType.js +24 -21
- data/webpack/JobWizard/steps/Schedule/StartEndDates.js +78 -23
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +402 -0
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +20 -10
- data/webpack/JobWizard/steps/Schedule/index.js +166 -29
- data/webpack/JobWizard/steps/form/DateTimePicker.js +126 -0
- data/webpack/JobWizard/steps/form/FormHelpers.js +4 -0
- data/webpack/JobWizard/steps/form/Formatter.js +49 -17
- data/webpack/JobWizard/steps/form/NumberInput.js +5 -2
- data/webpack/JobWizard/steps/form/ResourceSelect.js +29 -0
- data/webpack/JobWizard/steps/form/SearchSelect.js +121 -0
- data/webpack/JobWizard/steps/form/SelectField.js +14 -3
- data/webpack/JobWizard/steps/form/WizardTitle.js +14 -0
- data/webpack/JobWizard/steps/form/__tests__/SelectSearch.test.js +33 -0
- data/webpack/JobWizard/submit.js +120 -0
- data/webpack/JobWizard/validation.js +53 -0
- data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext/index.js +2 -0
- data/webpack/__mocks__/foremanReact/common/I18n.js +2 -0
- data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteActions.js +1 -0
- data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteConstants.js +1 -0
- data/webpack/__mocks__/foremanReact/routes/RouterSelector.js +1 -0
- data/webpack/helpers.js +1 -0
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +2 -1
- metadata +53 -7
- data/app/models/setting/remote_execution.rb +0 -88
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +0 -23
- 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,
|
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
|
|
@@ -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.
|
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 '>=
|
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,
|
data/package.json
CHANGED
@@ -21,18 +21,20 @@
|
|
21
21
|
},
|
22
22
|
"devDependencies": {
|
23
23
|
"@babel/core": "^7.7.0",
|
24
|
-
"@theforeman/builder": "^
|
25
|
-
"@theforeman/eslint-plugin-foreman": "^
|
26
|
-
"@theforeman/stories": "^
|
27
|
-
"@theforeman/test": "^
|
28
|
-
"@theforeman/vendor-dev": "^
|
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.
|
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
|
@@ -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
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|