foreman_remote_execution 4.7.0 → 5.1.0
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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +1 -0
- data/Gemfile +1 -1
- 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/lib/actions/remote_execution/run_host_job.rb +8 -1
- data/app/lib/actions/remote_execution/run_hosts_job.rb +57 -3
- data/app/lib/foreman_remote_execution/renderer/scope/input.rb +1 -1
- data/app/mailers/rex_job_mailer.rb +15 -0
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +8 -0
- data/app/models/job_invocation.rb +6 -0
- data/app/models/job_invocation_composer.rb +21 -13
- data/app/models/job_template.rb +3 -1
- data/app/models/remote_execution_provider.rb +18 -2
- data/app/models/rex_mail_notification.rb +13 -0
- data/app/models/targeting.rb +2 -2
- 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/refresh.js.erb +1 -0
- data/app/views/job_templates/_custom_tabs.html.erb +4 -9
- 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 +9 -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 +111 -6
- 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 +17 -14
- 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 +69 -2
- 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,13 @@ end
|
|
|
27
28
|
</div>
|
|
28
29
|
</div>
|
|
29
30
|
<% if @host %>
|
|
30
|
-
|
|
31
|
+
<% proxy_id = @template_invocation_task.input[:proxy_id] %>
|
|
32
|
+
<h3>
|
|
33
|
+
<%= _('Target: ') %><%= link_to(@host.name, host_path(@host)) %>
|
|
34
|
+
<% if proxy_id && proxy = SmartProxy.find_by(id: proxy_id) %>
|
|
35
|
+
<%= _('using Smart Proxy') %> <%= link_to(proxy.name, smart_proxy_path(proxy)) %>
|
|
36
|
+
<% end %>
|
|
37
|
+
</h3>
|
|
31
38
|
|
|
32
39
|
<div class="preview hidden">
|
|
33
40
|
<%= 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: 'Job invocation - report template',
|
|
156
|
+
full_name: N_('Job Invocation Report Template'),
|
|
157
|
+
collection: proc { ForemanRemoteExecution.job_invocation_report_templates_select }
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
64
161
|
# Add permissions
|
|
65
162
|
security_block :foreman_remote_execution do
|
|
66
163
|
permission :view_job_templates, { :job_templates => [:index, :show, :revision, :auto_complete_search, :auto_complete_job_category, :preview, :export],
|
|
67
164
|
:'api/v2/job_templates' => [:index, :show, :revision, :export],
|
|
68
165
|
:'api/v2/template_inputs' => [:index, :show],
|
|
69
166
|
:'api/v2/foreign_input_sets' => [:index, :show],
|
|
70
|
-
:ui_job_wizard => [:categories, :template]}, :resource_type => 'JobTemplate'
|
|
167
|
+
:ui_job_wizard => [:categories, :template, :resources]}, :resource_type => 'JobTemplate'
|
|
71
168
|
permission :create_job_templates, { :job_templates => [:new, :create, :clone_template, :import],
|
|
72
169
|
:'api/v2/job_templates' => [:create, :clone, :import] }, :resource_type => 'JobTemplate'
|
|
73
170
|
permission :edit_job_templates, { :job_templates => [:edit, :update],
|
|
@@ -86,6 +183,7 @@ module ForemanRemoteExecution
|
|
|
86
183
|
permission :view_template_invocations, { :template_invocations => [:show],
|
|
87
184
|
'api/v2/template_invocations' => [:template_invocations] }, :resource_type => 'TemplateInvocation'
|
|
88
185
|
permission :create_template_invocations, {}, :resource_type => 'TemplateInvocation'
|
|
186
|
+
permission :execute_jobs_on_infrastructure_hosts, {}, :resource_type => 'JobInvocation'
|
|
89
187
|
permission :cancel_job_invocations, { :job_invocations => [:cancel], 'api/v2/job_invocations' => [:cancel] }, :resource_type => 'JobInvocation'
|
|
90
188
|
# this permissions grants user to get auto completion hints when setting up filters
|
|
91
189
|
permission :filter_autocompletion_for_template_invocation, { :template_invocations => [ :auto_complete_search, :index ] },
|
|
@@ -146,6 +244,7 @@ module ForemanRemoteExecution
|
|
|
146
244
|
register_custom_status HostStatus::ExecutionStatus
|
|
147
245
|
# add dashboard widget
|
|
148
246
|
# widget 'foreman_remote_execution_widget', name: N_('Foreman plugin template widget'), sizex: 4, sizey: 1
|
|
247
|
+
widget 'dashboard/latest-jobs', :name => N_('Latest Jobs'), :sizex => 6, :sizey => 1
|
|
149
248
|
|
|
150
249
|
parameter_filter Subnet, :remote_execution_proxies, :remote_execution_proxy_ids => []
|
|
151
250
|
parameter_filter Nic::Interface do |ctx|
|
|
@@ -155,6 +254,8 @@ module ForemanRemoteExecution
|
|
|
155
254
|
register_graphql_query_field :job_invocations, '::Types::JobInvocation', :collection_field
|
|
156
255
|
register_graphql_query_field :job_invocation, '::Types::JobInvocation', :record_field
|
|
157
256
|
|
|
257
|
+
register_graphql_mutation_field :create_job_invocation, ::Mutations::JobInvocations::Create
|
|
258
|
+
|
|
158
259
|
extend_template_helpers ForemanRemoteExecution::RendererMethods
|
|
159
260
|
|
|
160
261
|
extend_rabl_template 'api/v2/smart_proxies/main', 'api/v2/smart_proxies/pubkey'
|
|
@@ -240,6 +341,10 @@ module ForemanRemoteExecution
|
|
|
240
341
|
end
|
|
241
342
|
end
|
|
242
343
|
|
|
344
|
+
def self.job_invocation_report_templates_select
|
|
345
|
+
Hash[ReportTemplate.unscoped.joins(:template_inputs).where(template_inputs: TemplateInput.where(name: 'job_id')).map { |template| [template.name, template.name] }]
|
|
346
|
+
end
|
|
347
|
+
|
|
243
348
|
def self.register_rex_feature
|
|
244
349
|
RemoteExecutionFeature.register(
|
|
245
350
|
: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
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
require 'test_plugin_helper'
|
|
2
2
|
|
|
3
3
|
class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
|
|
4
|
-
before do
|
|
5
|
-
Setting::RemoteExecution.load_defaults
|
|
6
|
-
end
|
|
7
4
|
let(:provider) { 'SSH' }
|
|
8
5
|
|
|
9
6
|
before { User.current = FactoryBot.build(:user, :admin) }
|
|
@@ -182,4 +179,40 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
|
|
|
182
179
|
end
|
|
183
180
|
end
|
|
184
181
|
end
|
|
182
|
+
|
|
183
|
+
describe '#execution_scope' do
|
|
184
|
+
let(:host) { FactoryBot.create(:host) }
|
|
185
|
+
let(:infra_host) { FactoryBot.create(:host, :with_infrastructure_facet) }
|
|
186
|
+
|
|
187
|
+
before do
|
|
188
|
+
host
|
|
189
|
+
infra_host
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
context 'without infrastructure host permission' do
|
|
193
|
+
it 'omits the infrastructure host' do
|
|
194
|
+
setup_user('view', 'hosts')
|
|
195
|
+
|
|
196
|
+
hosts = ::Host::Managed.execution_scope
|
|
197
|
+
hosts.must_include host
|
|
198
|
+
hosts.wont_include infra_host
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
context 'with infrastructure host permission' do
|
|
203
|
+
it 'finds the host as admin' do
|
|
204
|
+
assert User.current.admin?
|
|
205
|
+
hosts = ::Host::Managed.execution_scope
|
|
206
|
+
hosts.must_include host
|
|
207
|
+
hosts.must_include infra_host
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it 'finds the host as user with needed permissions' do
|
|
211
|
+
setup_user('execute_jobs_on', 'infrastructure_hosts')
|
|
212
|
+
hosts = ::Host::Managed.execution_scope
|
|
213
|
+
hosts.must_include host
|
|
214
|
+
hosts.must_include infra_host
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
185
218
|
end
|