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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +9 -0
  3. data/app/controllers/ui_job_wizard_controller.rb +16 -4
  4. data/app/graphql/mutations/job_invocations/create.rb +43 -0
  5. data/app/graphql/types/job_invocation_input.rb +13 -0
  6. data/app/graphql/types/recurrence_input.rb +8 -0
  7. data/app/graphql/types/scheduling_input.rb +6 -0
  8. data/app/graphql/types/targeting_enum.rb +7 -0
  9. data/app/lib/actions/remote_execution/run_host_job.rb +4 -0
  10. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +8 -0
  11. data/app/models/job_invocation_composer.rb +1 -1
  12. data/app/models/targeting.rb +2 -2
  13. data/app/views/job_invocations/refresh.js.erb +1 -0
  14. data/config/routes.rb +1 -0
  15. data/db/migrate/20210816100932_rex_setting_category_to_dsl.rb +5 -0
  16. data/lib/foreman_remote_execution/engine.rb +110 -6
  17. data/lib/foreman_remote_execution/version.rb +1 -1
  18. data/package.json +6 -6
  19. data/test/functional/api/v2/job_invocations_controller_test.rb +10 -0
  20. data/test/functional/cockpit_controller_test.rb +0 -1
  21. data/test/graphql/mutations/job_invocations/create.rb +58 -0
  22. data/test/helpers/remote_execution_helper_test.rb +0 -1
  23. data/test/unit/actions/run_host_job_test.rb +21 -0
  24. data/test/unit/concerns/host_extensions_test.rb +36 -3
  25. data/test/unit/job_invocation_composer_test.rb +3 -5
  26. data/test/unit/job_invocation_report_template_test.rb +1 -1
  27. data/test/unit/job_template_effective_user_test.rb +0 -4
  28. data/test/unit/remote_execution_provider_test.rb +0 -4
  29. data/test/unit/targeting_test.rb +68 -1
  30. data/webpack/JobWizard/JobWizard.js +94 -13
  31. data/webpack/JobWizard/JobWizard.scss +59 -35
  32. data/webpack/JobWizard/JobWizardConstants.js +28 -1
  33. data/webpack/JobWizard/JobWizardSelectors.js +32 -0
  34. data/webpack/JobWizard/__tests__/fixtures.js +81 -6
  35. data/webpack/JobWizard/__tests__/integration.test.js +26 -15
  36. data/webpack/JobWizard/__tests__/validation.test.js +141 -0
  37. data/webpack/JobWizard/autofill.js +38 -0
  38. data/webpack/JobWizard/index.js +7 -0
  39. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +7 -4
  40. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +32 -9
  41. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +216 -12
  42. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +82 -0
  43. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +1 -0
  44. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -2
  45. data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +62 -0
  46. data/webpack/JobWizard/steps/HostsAndInputs/HostSearch.js +54 -0
  47. data/webpack/JobWizard/steps/HostsAndInputs/SelectAPI.js +33 -0
  48. data/webpack/JobWizard/steps/HostsAndInputs/SelectGQL.js +52 -0
  49. data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +82 -7
  50. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +151 -0
  51. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +7 -4
  52. data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +18 -0
  53. data/webpack/JobWizard/steps/HostsAndInputs/hostgroups.gql +8 -0
  54. data/webpack/JobWizard/steps/HostsAndInputs/hosts.gql +8 -0
  55. data/webpack/JobWizard/steps/HostsAndInputs/index.js +182 -34
  56. data/webpack/JobWizard/steps/ReviewDetails/index.js +193 -0
  57. data/webpack/JobWizard/steps/Schedule/PurposeField.js +31 -0
  58. data/webpack/JobWizard/steps/Schedule/QueryType.js +46 -43
  59. data/webpack/JobWizard/steps/Schedule/RepeatCron.js +53 -0
  60. data/webpack/JobWizard/steps/Schedule/RepeatDaily.js +37 -0
  61. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +54 -0
  62. data/webpack/JobWizard/steps/Schedule/RepeatMonth.js +46 -0
  63. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +95 -31
  64. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +70 -0
  65. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +59 -19
  66. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +258 -11
  67. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +11 -2
  68. data/webpack/JobWizard/steps/Schedule/index.js +97 -21
  69. data/webpack/JobWizard/steps/form/DateTimePicker.js +41 -8
  70. data/webpack/JobWizard/steps/form/FormHelpers.js +4 -0
  71. data/webpack/JobWizard/steps/form/Formatter.js +39 -8
  72. data/webpack/JobWizard/steps/form/NumberInput.js +3 -2
  73. data/webpack/JobWizard/steps/form/ResourceSelect.js +29 -0
  74. data/webpack/JobWizard/steps/form/SearchSelect.js +121 -0
  75. data/webpack/JobWizard/steps/form/SelectField.js +14 -3
  76. data/webpack/JobWizard/steps/form/__tests__/SelectSearch.test.js +33 -0
  77. data/webpack/JobWizard/submit.js +120 -0
  78. data/webpack/JobWizard/validation.js +53 -0
  79. data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext/index.js +2 -0
  80. data/webpack/__mocks__/foremanReact/common/I18n.js +2 -0
  81. data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteActions.js +1 -0
  82. data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteConstants.js +1 -0
  83. data/webpack/__mocks__/foremanReact/routes/RouterSelector.js +1 -0
  84. data/webpack/helpers.js +1 -0
  85. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +1 -1
  86. metadata +38 -6
  87. data/app/models/setting/remote_execution.rb +0 -94
  88. data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +0 -23
  89. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/SelectedChips.test.js +0 -37
  90. 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: c7565defbf0881174acb2a48e274aacb3d790d2ee8b9de3119307c2afabd7355
4
- data.tar.gz: acffdb2df04d0c1caeff65513e1a63e5cf66276897206affd606c5136b7afba8
3
+ metadata.gz: 66efbd042b1d607eeaa4e72b07452ac00a4e68d28bf09e1fc496f26cae25f1e4
4
+ data.tar.gz: 124621a113adab399e259ece92caad11fdf35da117da77dc512fc9e31e6ec66a
5
5
  SHA512:
6
- metadata.gz: 7064c4e3984fd4ef3ef463fc10cb27e911d1f24ac0b74ccf5fb3b7682ba3d4c3418e130f5b6e0f1920ed83406dbf0787585ef1e1b83cfd0c0e412219c059309c
7
- data.tar.gz: ab22827c2750d67893621267a43095c2b845937b6d72d5a665bf800ab97542a06cda1480187174943077c9365caac0076d67f765b6c43c4e01650dca1f7b9010
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 < ::Api::V2::BaseController
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 map_template_inputs(template_inputs_with_foreign)
27
- template_inputs_with_foreign.map { |input| input.attributes.merge({:resource_type => input.resource_type&.tableize }) }
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
@@ -0,0 +1,8 @@
1
+ module Types
2
+ class RecurrenceInput < BaseInputObject
3
+ argument :cron_line, String, required: false
4
+ argument :max_iteration, Integer, required: false
5
+ argument :end_time, String, required: false
6
+ argument :purpose, String, required: false
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ module Types
2
+ class SchedulingInput < BaseInputObject
3
+ argument :start_at, String, required: false
4
+ argument :start_before, String, required: false
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module Types
2
+ class TargetingEnum < Types::BaseEnum
3
+ Targeting::TYPES.each_key do |key|
4
+ value key
5
+ end
6
+ end
7
+ 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
 
@@ -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
 
@@ -1,3 +1,4 @@
1
1
  $('form#job_invocation_form').replaceWith("<%=j render :partial => 'form' %>");
2
2
  $('#job_invocation_form').find('a[rel="popover"]').popover();
3
3
  $('#job_invocation_form select:not(.without_select2)').select2({ allowClear: true });
4
+ $('div.tooltip').remove();
data/config/routes.rb CHANGED
@@ -45,6 +45,7 @@ Rails.application.routes.draw do
45
45
  get 'cockpit/redirect', to: 'cockpit#redirect'
46
46
  get 'ui_job_wizard/categories', to: 'ui_job_wizard#categories'
47
47
  get 'ui_job_wizard/template/:id', to: 'ui_job_wizard#template'
48
+ get 'ui_job_wizard/resources', to: 'ui_job_wizard#resources'
48
49
 
49
50
  match '/experimental/job_wizard', to: 'react#index', :via => [:get]
50
51
 
@@ -0,0 +1,5 @@
1
+ class RexSettingCategoryToDsl < ActiveRecord::Migration[6.0]
2
+ def up
3
+ Setting.where(category: 'Setting::RemoteExecution').update_all(category: 'Setting')
4
+ end
5
+ end
@@ -19,10 +19,6 @@ module ForemanRemoteExecution
19
19
  end
20
20
  assets_to_precompile += %w(foreman_remote_execution/foreman_remote_execution.css)
21
21
 
22
- initializer 'foreman_remote_execution.load_default_settings', :before => :load_config_initializers do
23
- require_dependency File.expand_path('../../../app/models/setting/remote_execution.rb', __FILE__) if (Setting.table_exists? rescue(false))
24
- end
25
-
26
22
  # Add any db migrations
27
23
  initializer 'foreman_remote_execution.load_app_instance_data' do |app|
28
24
  ForemanRemoteExecution::Engine.paths['db/migrate'].existent.each do |path|
@@ -51,7 +47,7 @@ module ForemanRemoteExecution
51
47
 
52
48
  initializer 'foreman_remote_execution.register_plugin', before: :finisher_hook do |_app|
53
49
  Foreman::Plugin.register :foreman_remote_execution do
54
- requires_foreman '>= 2.2'
50
+ requires_foreman '>= 3.1'
55
51
  register_global_js_file 'global'
56
52
 
57
53
  apipie_documented_controllers ["#{ForemanRemoteExecution::Engine.root}/app/controllers/api/v2/*.rb"]
@@ -61,13 +57,114 @@ module ForemanRemoteExecution
61
57
  automatic_assets(false)
62
58
  precompile_assets(*assets_to_precompile)
63
59
 
60
+ # Add settings to a Remote Execution category
61
+ settings do
62
+ category :remote_execution, N_('Remote Execution') do
63
+ setting 'remote_execution_fallback_proxy',
64
+ type: :boolean,
65
+ description: N_('Search the host for any proxy with Remote Execution, useful when the host has no subnet or the subnet does not have an execution proxy'),
66
+ default: false,
67
+ full_name: N_('Fallback to Any Proxy')
68
+ setting 'remote_execution_global_proxy',
69
+ type: :boolean,
70
+ description: N_('Search for remote execution proxy outside of the proxies assigned to the host. The search will be limited to the host\'s organization and location.'),
71
+ default: true,
72
+ full_name: N_('Enable Global Proxy')
73
+ setting 'remote_execution_ssh_user',
74
+ type: :string,
75
+ description: N_('Default user to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_user.'),
76
+ default: 'root',
77
+ full_name: N_('SSH User')
78
+ setting 'remote_execution_effective_user',
79
+ type: :string,
80
+ description: N_('Default user to use for executing the script. If the user differs from the SSH user, su or sudo is used to switch the user.'),
81
+ default: 'root',
82
+ full_name: N_('Efffective User')
83
+ setting 'remote_execution_effective_user_method',
84
+ type: :string,
85
+ description: N_('What command should be used to switch to the effective user. One of %s') % SSHExecutionProvider::EFFECTIVE_USER_METHODS.inspect,
86
+ default: 'sudo',
87
+ full_name: N_('Effective User Method'),
88
+ collection: proc { Hash[SSHExecutionProvider::EFFECTIVE_USER_METHODS.map { |method| [method, method] }] }
89
+ setting 'remote_execution_effective_user_password',
90
+ type: :string,
91
+ description: N_('Effective user password'),
92
+ default: '',
93
+ full_name: N_('Effective user password'),
94
+ encrypted: true
95
+ setting 'remote_execution_sync_templates',
96
+ type: :boolean,
97
+ description: N_('Whether we should sync templates from disk when running db:seed.'),
98
+ default: true,
99
+ full_name: N_('Sync Job Templates')
100
+ setting 'remote_execution_ssh_port',
101
+ type: :integer,
102
+ description: N_('Port to use for SSH communication. Default port 22. You may override per host by setting a parameter called remote_execution_ssh_port.'),
103
+ default: 22,
104
+ full_name: N_('SSH Port')
105
+ setting 'remote_execution_connect_by_ip',
106
+ type: :boolean,
107
+ description: N_('Should the ip addresses on host interfaces be preferred over the fqdn? '\
108
+ 'It is useful when DNS not resolving the fqdns properly. You may override this per host by setting a parameter called remote_execution_connect_by_ip. '\
109
+ 'For dual-stacked hosts you should consider the remote_execution_connect_by_ip_prefer_ipv6 setting'),
110
+ default: false,
111
+ full_name: N_('Connect by IP')
112
+ setting 'remote_execution_connect_by_ip_prefer_ipv6',
113
+ type: :boolean,
114
+ description: N_('When connecting using ip address, should the IPv6 addresses be preferred? '\
115
+ 'If no IPv6 address is set, it falls back to IPv4 automatically. You may override this per host by setting a parameter called remote_execution_connect_by_ip_prefer_ipv6. '\
116
+ 'By default and for compatibility, IPv4 will be preferred over IPv6 by default'),
117
+ default: false,
118
+ full_name: N_('Prefer IPv6 over IPv4')
119
+ setting 'remote_execution_ssh_password',
120
+ type: :string,
121
+ description: N_('Default password to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_password'),
122
+ default: nil,
123
+ full_name: N_('Default SSH password'),
124
+ encrypted: true
125
+ setting 'remote_execution_ssh_key_passphrase',
126
+ type: :string,
127
+ description: N_('Default key passphrase to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_key_passphrase'),
128
+ default: nil,
129
+ full_name: N_('Default SSH key passphrase'),
130
+ encrypted: true
131
+ setting 'remote_execution_workers_pool_size',
132
+ type: :integer,
133
+ description: N_('Amount of workers in the pool to handle the execution of the remote execution jobs. Restart of the dynflowd/foreman-tasks service is required.'),
134
+ default: 5,
135
+ full_name: N_('Workers pool size')
136
+ setting 'remote_execution_cleanup_working_dirs',
137
+ type: :boolean,
138
+ description: N_('When enabled, working directories will be removed after task completion. You may override this per host by setting a parameter called remote_execution_cleanup_working_dirs.'),
139
+ default: true,
140
+ full_name: N_('Cleanup working directories')
141
+ setting 'remote_execution_cockpit_url',
142
+ type: :string,
143
+ description: N_('Where to find the Cockpit instance for the Web Console button. By default, no button is shown.'),
144
+ default: nil,
145
+ full_name: N_('Cockpit URL')
146
+ setting 'remote_execution_form_job_template',
147
+ type: :string,
148
+ description: N_('Choose a job template that is pre-selected in job invocation form'),
149
+ default: 'Run Command - SSH Default',
150
+ full_name: N_('Form Job Template'),
151
+ collection: proc { Hash[JobTemplate.unscoped.map { |template| [template.name, template.name] }] }
152
+ setting 'remote_execution_job_invocation_report_template',
153
+ type: :string,
154
+ description: N_('Select a report template used for generating a report for a particular remote execution job'),
155
+ default: 'Jobs - Invocation report template',
156
+ full_name: N_('Job Invocation Report Template'),
157
+ collection: proc { ForemanRemoteExecution.job_invocation_report_templates_select }
158
+ end
159
+ end
160
+
64
161
  # Add permissions
65
162
  security_block :foreman_remote_execution do
66
163
  permission :view_job_templates, { :job_templates => [:index, :show, :revision, :auto_complete_search, :auto_complete_job_category, :preview, :export],
67
164
  :'api/v2/job_templates' => [:index, :show, :revision, :export],
68
165
  :'api/v2/template_inputs' => [:index, :show],
69
166
  :'api/v2/foreign_input_sets' => [:index, :show],
70
- :ui_job_wizard => [:categories, :template]}, :resource_type => 'JobTemplate'
167
+ :ui_job_wizard => [:categories, :template, :resources]}, :resource_type => 'JobTemplate'
71
168
  permission :create_job_templates, { :job_templates => [:new, :create, :clone_template, :import],
72
169
  :'api/v2/job_templates' => [:create, :clone, :import] }, :resource_type => 'JobTemplate'
73
170
  permission :edit_job_templates, { :job_templates => [:edit, :update],
@@ -86,6 +183,7 @@ module ForemanRemoteExecution
86
183
  permission :view_template_invocations, { :template_invocations => [:show],
87
184
  'api/v2/template_invocations' => [:template_invocations] }, :resource_type => 'TemplateInvocation'
88
185
  permission :create_template_invocations, {}, :resource_type => 'TemplateInvocation'
186
+ permission :execute_jobs_on_infrastructure_hosts, {}, :resource_type => 'JobInvocation'
89
187
  permission :cancel_job_invocations, { :job_invocations => [:cancel], 'api/v2/job_invocations' => [:cancel] }, :resource_type => 'JobInvocation'
90
188
  # this permissions grants user to get auto completion hints when setting up filters
91
189
  permission :filter_autocompletion_for_template_invocation, { :template_invocations => [ :auto_complete_search, :index ] },
@@ -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,
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '4.8.0'.freeze
2
+ VERSION = '5.0.0'.freeze
3
3
  end
data/package.json CHANGED
@@ -21,11 +21,11 @@
21
21
  },
22
22
  "devDependencies": {
23
23
  "@babel/core": "^7.7.0",
24
- "@theforeman/builder": "^8.10.0",
25
- "@theforeman/eslint-plugin-foreman": "^8.10.0",
26
- "@theforeman/stories": "^8.10.0",
27
- "@theforeman/test": "^8.10.0",
28
- "@theforeman/vendor-dev": "^8.10.0",
24
+ "@theforeman/builder": "^8.16.0",
25
+ "@theforeman/eslint-plugin-foreman": "^8.16.0",
26
+ "@theforeman/stories": "^8.16.0",
27
+ "@theforeman/test": "^8.16.0",
28
+ "@theforeman/vendor-dev": "^8.16.0",
29
29
  "babel-eslint": "^10.0.0",
30
30
  "eslint": "^6.8.0",
31
31
  "prettier": "^1.19.1",
@@ -33,6 +33,6 @@
33
33
  "redux-mock-store": "^1.2.2"
34
34
  },
35
35
  "peerDependencies": {
36
- "@theforeman/vendor": "^8.10.0"
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
@@ -2,7 +2,6 @@ require 'test_plugin_helper'
2
2
 
3
3
  class CockpitControllerTest < ActionController::TestCase
4
4
  def setup
5
- Setting::RemoteExecution.load_defaults
6
5
  as_admin do
7
6
  @host = FactoryBot.create(:host)
8
7
  end
@@ -0,0 +1,58 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module Mutations
4
+ module JobInvocations
5
+ class CreateMutationTest < ActiveSupport::TestCase
6
+ let(:host) { FactoryBot.create(:host) }
7
+ let(:job_template) { FactoryBot.create(:job_template, :with_input) }
8
+ let(:cron_line) { '5 * * * *' }
9
+ let(:purpose) { 'test' }
10
+ let(:variables) do
11
+ {
12
+ jobInvocation: {
13
+ hostIds: [host.id],
14
+ jobTemplateId: job_template.id,
15
+ targetingType: 'static_query',
16
+ inputs: { job_template.template_inputs.first.name => "bar" },
17
+ recurrence: {
18
+ cronLine: cron_line,
19
+ purpose: purpose,
20
+ },
21
+ },
22
+ }
23
+ end
24
+
25
+ let(:query) do
26
+ <<-GRAPHQL
27
+ mutation CreateJobInvocation($jobInvocation: JobInvocationInput!) {
28
+ createJobInvocation(input: { jobInvocation: $jobInvocation }) {
29
+ jobInvocation {
30
+ id
31
+ description
32
+ recurringLogic {
33
+ cronLine
34
+ purpose
35
+ }
36
+ }
37
+ }
38
+ }
39
+ GRAPHQL
40
+ end
41
+
42
+ context 'with admin user' do
43
+ let(:user) { FactoryBot.create(:user, :admin) }
44
+ let(:context) { { current_user: user } }
45
+
46
+ test 'create a job invocation' do
47
+ assert_difference('JobInvocation.count', +1) do
48
+ result = ForemanGraphqlSchema.execute(query, variables: variables, context: context)
49
+ assert_empty result['errors']
50
+ assert_empty result['data']['createJobInvocation']['jobInvocation']['errors']
51
+ assert_equal cron_line, result['data']['createJobInvocation']['jobInvocation']['recurringLogic']['cronLine']
52
+ assert_equal purpose, result['data']['createJobInvocation']['jobInvocation']['recurringLogic']['purpose']
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -26,7 +26,6 @@ class RemoteExecutionHelperTest < ActionView::TestCase
26
26
 
27
27
  describe 'test correct setting' do
28
28
  it 'should found correct template from setting' do
29
- Setting::RemoteExecution.load_defaults
30
29
  template_name = 'Job Invocation Report Template'
31
30
  setting_key = 'remote_execution_job_invocation_report_template'
32
31
  template = FactoryBot.create(:report_template, name: template_name)
@@ -36,6 +36,27 @@ module ForemanRemoteExecution
36
36
  end
37
37
  end
38
38
 
39
+ describe '#verify_permission' do
40
+ let(:job_invocation) { FactoryBot.create(:job_invocation, :with_task) }
41
+ let(:template_invocation) { job_invocation.template_invocations.first }
42
+
43
+ before { job_invocation }
44
+
45
+ it 'raises an exception when run against an infrastructure host' do
46
+ template_invocation.host = FactoryBot.create(:host, :with_infrastructure_facet)
47
+
48
+ setup_user('view', 'hosts')
49
+ setup_user('view', 'job_templates')
50
+ setup_user('create', 'template_invocations')
51
+
52
+ action = Actions::RemoteExecution::RunHostJob.allocate
53
+ exception = assert_raises do
54
+ action.send(:verify_permissions, template_invocation.host, template_invocation)
55
+ end
56
+ _(exception.message).must_include "infrastructure host #{template_invocation.host.name}"
57
+ end
58
+ end
59
+
39
60
  describe '#finalize' do
40
61
  let(:host) { FactoryBot.create(:host, :with_execution) }
41
62
 
@@ -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(:authorized).with(:view_hosts, Host).returns(Host.where({}))
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 = Setting::RemoteExecution.job_invocation_report_templates_select
22
+ templates = ForemanRemoteExecution.job_invocation_report_templates_select
23
23
 
24
24
  assert_include templates, 'Job Invocation Report Template'
25
25
  end