foreman_remote_execution 4.8.0 → 5.0.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/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
|