foreman_remote_execution 4.8.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/job_invocations_controller.rb +9 -0
- 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 +4 -0
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +8 -0
- data/app/models/job_invocation_composer.rb +1 -1
- data/app/models/targeting.rb +2 -2
- data/app/views/job_invocations/refresh.js.erb +1 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20210816100932_rex_setting_category_to_dsl.rb +5 -0
- data/lib/foreman_remote_execution/engine.rb +110 -6
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/package.json +6 -6
- data/test/functional/api/v2/job_invocations_controller_test.rb +10 -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/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 +1 -1
- data/test/unit/job_template_effective_user_test.rb +0 -4
- data/test/unit/remote_execution_provider_test.rb +0 -4
- data/test/unit/targeting_test.rb +68 -1
- data/webpack/JobWizard/JobWizard.js +94 -13
- data/webpack/JobWizard/JobWizard.scss +59 -35
- data/webpack/JobWizard/JobWizardConstants.js +28 -1
- data/webpack/JobWizard/JobWizardSelectors.js +32 -0
- data/webpack/JobWizard/__tests__/fixtures.js +81 -6
- data/webpack/JobWizard/__tests__/integration.test.js +26 -15
- 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 +7 -4
- data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +32 -9
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +216 -12
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +82 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +1 -0
- 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 +82 -7
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +151 -0
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +7 -4
- 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 +182 -34
- 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/StartEndDates.js +59 -19
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +258 -11
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +11 -2
- data/webpack/JobWizard/steps/Schedule/index.js +97 -21
- data/webpack/JobWizard/steps/form/DateTimePicker.js +41 -8
- data/webpack/JobWizard/steps/form/FormHelpers.js +4 -0
- data/webpack/JobWizard/steps/form/Formatter.js +39 -8
- data/webpack/JobWizard/steps/form/NumberInput.js +3 -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/__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 +1 -1
- metadata +38 -6
- data/app/models/setting/remote_execution.rb +0 -94
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +0 -23
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/SelectedChips.test.js +0 -37
- data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +0 -76
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66efbd042b1d607eeaa4e72b07452ac00a4e68d28bf09e1fc496f26cae25f1e4
|
4
|
+
data.tar.gz: 124621a113adab399e259ece92caad11fdf35da117da77dc512fc9e31e6ec66a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04ed33fb87e3b0830501f97b95ab62c129897e8510909b47163559854bb0b2b792ccab4035c5fc606a6a8d8e5ff8c084fcfd2109331dff8b4e7a6eac23c86825
|
7
|
+
data.tar.gz: bca23be551bd1d8169c28a5a4e2d71079ea42cae3a11d6d289f40c9c862a2e7307b6c6e569ba79be7e743f729ef58a3ef72b027ee8735e9c448e4672a105db4b
|
@@ -168,6 +168,15 @@ module Api
|
|
168
168
|
render :json => { :outputs => outputs }
|
169
169
|
end
|
170
170
|
|
171
|
+
def resource_name(resource = controller_name)
|
172
|
+
case resource
|
173
|
+
when 'organization', 'location'
|
174
|
+
nil
|
175
|
+
else
|
176
|
+
'job_invocation'
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
171
180
|
private
|
172
181
|
|
173
182
|
def allowed_nested_id
|
@@ -1,11 +1,12 @@
|
|
1
|
-
class UiJobWizardController <
|
1
|
+
class UiJobWizardController < ApplicationController
|
2
|
+
include FiltersHelper
|
2
3
|
def categories
|
3
4
|
job_categories = resource_scope
|
4
5
|
.search_for("job_category ~ \"#{params[:search]}\"")
|
5
6
|
.select(:job_category).distinct
|
6
7
|
.reorder(:job_category)
|
7
8
|
.pluck(:job_category)
|
8
|
-
render :json => {:job_categories =>job_categories}
|
9
|
+
render :json => {:job_categories =>job_categories, :with_katello => with_katello}
|
9
10
|
end
|
10
11
|
|
11
12
|
def template
|
@@ -19,12 +20,16 @@ class UiJobWizardController < ::Api::V2::BaseController
|
|
19
20
|
}
|
20
21
|
end
|
21
22
|
|
23
|
+
def map_template_inputs(template_inputs_with_foreign)
|
24
|
+
template_inputs_with_foreign.map { |input| input.attributes.merge({:resource_type_tableize => input.resource_type&.tableize }) }
|
25
|
+
end
|
26
|
+
|
22
27
|
def resource_name(nested_resource = nil)
|
23
28
|
nested_resource || 'job_template'
|
24
29
|
end
|
25
30
|
|
26
|
-
def
|
27
|
-
|
31
|
+
def with_katello
|
32
|
+
!!defined?(::Katello)
|
28
33
|
end
|
29
34
|
|
30
35
|
def resource_class
|
@@ -34,4 +39,11 @@ class UiJobWizardController < ::Api::V2::BaseController
|
|
34
39
|
def action_permission
|
35
40
|
:view_job_templates
|
36
41
|
end
|
42
|
+
|
43
|
+
def resources
|
44
|
+
resource_type = params[:resource]
|
45
|
+
resource_list = resource_type.constantize.authorized("view_#{resource_type.underscore.pluralize}").all.map { |r| {:name => r.to_s, :id => r.id } }.select { |v| v[:name] =~ /#{params[:name]}/ }
|
46
|
+
render :json => { :results =>
|
47
|
+
resource_list.sort_by { |r| r[:name] }.take(100), :subtotal => resource_list.count}
|
48
|
+
end
|
37
49
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Mutations
|
2
|
+
module JobInvocations
|
3
|
+
class Create < ::Mutations::CreateMutation
|
4
|
+
description 'Create a new job invocation'
|
5
|
+
graphql_name 'CreateJobInvocationMutation'
|
6
|
+
|
7
|
+
argument :job_invocation, ::Types::JobInvocationInput, required: true
|
8
|
+
field :job_invocation, ::Types::JobInvocation, 'The new job invocation', null: true
|
9
|
+
|
10
|
+
def resolve(params)
|
11
|
+
begin
|
12
|
+
composer = JobInvocationComposer.from_api_params(params[:job_invocation].to_h)
|
13
|
+
job_invocation = composer.job_invocation
|
14
|
+
authorize!(job_invocation, :create)
|
15
|
+
errors = []
|
16
|
+
composer.trigger!
|
17
|
+
rescue ActiveRecord::RecordNotSaved
|
18
|
+
errors = map_all_errors(job_invocation)
|
19
|
+
rescue JobInvocationComposer::JobTemplateNotFound, JobInvocationComposer::FeatureNotFound => e
|
20
|
+
errors = [{ path => ['job_template'], :message => e.message }]
|
21
|
+
end
|
22
|
+
|
23
|
+
{
|
24
|
+
result_key => job_invocation,
|
25
|
+
:errors => errors,
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def map_all_errors(job_invocation)
|
30
|
+
map_errors_to_path(job_invocation) + map_errors('triggering', job_invocation.triggering) + map_errors('targeting', job_invocation.targeting)
|
31
|
+
end
|
32
|
+
|
33
|
+
def map_errors(attr_name, resource)
|
34
|
+
resource.errors.map do |attribute, message|
|
35
|
+
{
|
36
|
+
path: [attr_name, attribute.to_s.camelize(:lower)],
|
37
|
+
message: message,
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Types
|
2
|
+
class JobInvocationInput < BaseInputObject
|
3
|
+
argument :job_category, String, required: false
|
4
|
+
argument :job_template_id, Integer, required: false
|
5
|
+
argument :feature, String, required: false
|
6
|
+
argument :targeting_type, ::Types::TargetingEnum, required: false
|
7
|
+
argument :recurrence, ::Types::RecurrenceInput, required: false
|
8
|
+
argument :scheduling, ::Types::SchedulingInput, required: false
|
9
|
+
argument :search_query, String, required: false
|
10
|
+
argument :host_ids, [Integer], required: false
|
11
|
+
argument :inputs, ::Types::RawJson, required: false
|
12
|
+
end
|
13
|
+
end
|
@@ -196,6 +196,10 @@ module Actions
|
|
196
196
|
def verify_permissions(host, template_invocation)
|
197
197
|
raise _('User can not execute job on host %s') % host.name unless User.current.can?(:view_hosts, host)
|
198
198
|
raise _('User can not execute this job template') unless User.current.can?(:view_job_templates, template_invocation.template)
|
199
|
+
infra_facet = host.infrastructure_facet
|
200
|
+
if (infra_facet&.foreman_instance || infra_facet&.smart_proxy_id) && !User.current.can?(:execute_jobs_on_infrastructure_hosts)
|
201
|
+
raise _('User can not execute job on infrastructure host %s') % host.name
|
202
|
+
end
|
199
203
|
|
200
204
|
# we don't want to load all template_invocations to verify so we construct Authorizer object manually and set
|
201
205
|
# the base collection to current template
|
@@ -20,6 +20,14 @@ module ForemanRemoteExecution
|
|
20
20
|
scoped_search :relation => :execution_status_object, :on => :status, :rename => :execution_status,
|
21
21
|
:complete_value => { :ok => HostStatus::ExecutionStatus::OK, :error => HostStatus::ExecutionStatus::ERROR }
|
22
22
|
|
23
|
+
scope :execution_scope, lambda {
|
24
|
+
if User.current&.can?('execute_jobs_on_infrastructure_hosts')
|
25
|
+
self
|
26
|
+
else
|
27
|
+
search_for('not (infrastructure_facet.foreman = true or set? infrastructure_facet.smart_proxy_id)')
|
28
|
+
end
|
29
|
+
}
|
30
|
+
|
23
31
|
def search_by_job_invocation(key, operator, value)
|
24
32
|
if key == 'job_invocation.result'
|
25
33
|
operator = operator == '=' ? 'IN' : 'NOT IN'
|
@@ -528,7 +528,7 @@ class JobInvocationComposer
|
|
528
528
|
if displayed_search_query.blank?
|
529
529
|
Host.where('1 = 0')
|
530
530
|
else
|
531
|
-
Host.authorized(Targeting::RESOLVE_PERMISSION, Host).search_for(displayed_search_query)
|
531
|
+
Host.execution_scope.authorized(Targeting::RESOLVE_PERMISSION, Host).search_for(displayed_search_query)
|
532
532
|
end
|
533
533
|
end
|
534
534
|
|
data/app/models/targeting.rb
CHANGED
@@ -44,7 +44,7 @@ class Targeting < ApplicationRecord
|
|
44
44
|
self.validate!
|
45
45
|
# avoid validation of hosts objects - they will be loaded for no reason.
|
46
46
|
# pluck(:id) returns duplicate results for HostCollections
|
47
|
-
host_ids = User.as(user.login) { Host.authorized(RESOLVE_PERMISSION, Host).search_for(search_query).order(:name, :id).pluck(:id).uniq }
|
47
|
+
host_ids = User.as(user.login) { Host.execution_scope.authorized(RESOLVE_PERMISSION, Host).search_for(search_query).order(:name, :id).pluck(:id).uniq }
|
48
48
|
host_ids.shuffle!(random: Random.new) if randomized_ordering
|
49
49
|
self.assign_host_ids(host_ids)
|
50
50
|
self.save(:validate => false)
|
@@ -66,7 +66,7 @@ class Targeting < ApplicationRecord
|
|
66
66
|
def self.build_query_from_hosts(ids)
|
67
67
|
return '' if ids.empty?
|
68
68
|
|
69
|
-
hosts = Host.where(:id => ids).distinct.pluck(:name)
|
69
|
+
hosts = Host.execution_scope.where(:id => ids).distinct.pluck(:name)
|
70
70
|
"name ^ (#{hosts.join(', ')})"
|
71
71
|
end
|
72
72
|
|
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
|
|
@@ -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 ] },
|
@@ -156,6 +254,8 @@ module ForemanRemoteExecution
|
|
156
254
|
register_graphql_query_field :job_invocations, '::Types::JobInvocation', :collection_field
|
157
255
|
register_graphql_query_field :job_invocation, '::Types::JobInvocation', :record_field
|
158
256
|
|
257
|
+
register_graphql_mutation_field :create_job_invocation, ::Mutations::JobInvocations::Create
|
258
|
+
|
159
259
|
extend_template_helpers ForemanRemoteExecution::RendererMethods
|
160
260
|
|
161
261
|
extend_rabl_template 'api/v2/smart_proxies/main', 'api/v2/smart_proxies/pubkey'
|
@@ -241,6 +341,10 @@ module ForemanRemoteExecution
|
|
241
341
|
end
|
242
342
|
end
|
243
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
|
+
|
244
348
|
def self.register_rex_feature
|
245
349
|
RemoteExecutionFeature.register(
|
246
350
|
:puppet_run_host,
|
data/package.json
CHANGED
@@ -21,11 +21,11 @@
|
|
21
21
|
},
|
22
22
|
"devDependencies": {
|
23
23
|
"@babel/core": "^7.7.0",
|
24
|
-
"@theforeman/builder": "^8.
|
25
|
-
"@theforeman/eslint-plugin-foreman": "^8.
|
26
|
-
"@theforeman/stories": "^8.
|
27
|
-
"@theforeman/test": "^8.
|
28
|
-
"@theforeman/vendor-dev": "^8.
|
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",
|
@@ -33,6 +33,6 @@
|
|
33
33
|
"redux-mock-store": "^1.2.2"
|
34
34
|
},
|
35
35
|
"peerDependencies": {
|
36
|
-
"@theforeman/vendor": "^8.
|
36
|
+
"@theforeman/vendor": "^8.16.0"
|
37
37
|
}
|
38
38
|
}
|
@@ -191,6 +191,11 @@ module Api
|
|
191
191
|
host_id: FactoryBot.create(:host).id }
|
192
192
|
assert_response :missing
|
193
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
|
194
199
|
end
|
195
200
|
|
196
201
|
describe '#outputs' do
|
@@ -243,6 +248,11 @@ module Api
|
|
243
248
|
assert_response :success
|
244
249
|
end
|
245
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
|
+
|
246
256
|
test 'should provide raw output for delayed task' do
|
247
257
|
start_time = Time.now
|
248
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
|
|
@@ -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
|
@@ -270,10 +270,6 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
270
270
|
composer.pattern_template_invocations.first
|
271
271
|
end
|
272
272
|
|
273
|
-
before do
|
274
|
-
Setting::RemoteExecution.load_defaults
|
275
|
-
end
|
276
|
-
|
277
273
|
context 'when overridable and provided' do
|
278
274
|
let(:overridable) { true }
|
279
275
|
let(:invocation_effective_user) { 'invocation user' }
|
@@ -370,8 +366,10 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
370
366
|
let(:host) { FactoryBot.create(:host) }
|
371
367
|
|
372
368
|
it 'obeys authorization' do
|
369
|
+
fake_scope = mock
|
373
370
|
composer.stubs(:displayed_search_query => "name = #{host.name}")
|
374
|
-
Host.expects(:
|
371
|
+
Host.expects(:execution_scope).returns(fake_scope)
|
372
|
+
fake_scope.expects(:authorized).with(:view_hosts, Host).returns(Host.where({}))
|
375
373
|
composer.targeted_hosts_count
|
376
374
|
end
|
377
375
|
|
@@ -19,7 +19,7 @@ class JobReportTemplateTest < ActiveSupport::TestCase
|
|
19
19
|
it 'in settings includes only report templates with job_id input' do
|
20
20
|
FactoryBot.create(:report_template, name: 'Template 1')
|
21
21
|
job_invocation_template
|
22
|
-
templates =
|
22
|
+
templates = ForemanRemoteExecution.job_invocation_report_templates_select
|
23
23
|
|
24
24
|
assert_include templates, 'Job Invocation Report Template'
|
25
25
|
end
|