foreman_remote_execution 3.3.5 → 4.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/app/controllers/api/v2/job_invocations_controller.rb +20 -2
- data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_controller_extensions.rb +26 -0
- data/app/controllers/foreman_remote_execution/concerns/api/v2/subnets_controller_extensions.rb +21 -0
- data/app/controllers/job_invocations_controller.rb +22 -8
- data/app/controllers/job_templates_controller.rb +1 -1
- data/app/helpers/job_invocations_helper.rb +3 -2
- data/app/lib/actions/remote_execution/run_host_job.rb +1 -1
- data/app/lib/actions/remote_execution/run_hosts_job.rb +12 -3
- data/app/lib/foreman_remote_execution/renderer/scope/input.rb +35 -0
- data/app/models/concerns/api/v2/interfaces_controller_extensions.rb +13 -0
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +38 -14
- data/app/models/job_invocation.rb +12 -4
- data/app/models/job_invocation_composer.rb +2 -2
- data/app/models/remote_execution_feature.rb +5 -2
- data/app/models/remote_execution_provider.rb +8 -3
- data/app/models/setting/remote_execution.rb +2 -2
- data/app/models/ssh_execution_provider.rb +1 -1
- data/app/services/remote_execution_proxy_selector.rb +3 -0
- data/app/views/api/v2/interfaces/execution_flag.json.rabl +1 -0
- data/app/views/api/v2/job_invocations/base.json.rabl +1 -0
- data/app/views/api/v2/job_invocations/main.json.rabl +2 -2
- data/app/views/api/v2/registration/_form.html.erb +12 -0
- data/app/views/api/v2/subnets/remote_execution_proxies.json.rabl +3 -0
- data/app/views/job_invocations/_form.html.erb +1 -1
- data/app/views/job_invocations/_tab_hosts.html.erb +1 -20
- data/app/views/job_invocations/_tab_overview.html.erb +13 -1
- data/app/views/job_invocations/show.html.erb +3 -0
- data/app/views/job_invocations/show.json.erb +2 -1
- data/app/views/template_invocations/_output_line_set.html.erb +1 -1
- data/app/views/templates/ssh/package_action.erb +1 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20200623073022_rename_sudo_password_to_effective_user_password.rb +34 -0
- data/db/migrate/20200820122057_add_proxy_selector_override_to_remote_execution_feature.rb +5 -0
- data/db/seeds.d/20-permissions.rb +9 -0
- data/lib/foreman_remote_execution/engine.rb +28 -2
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/package.json +6 -6
- data/test/functional/api/v2/job_invocations_controller_test.rb +84 -2
- data/test/functional/api/v2/registration_controller_test.rb +82 -0
- data/test/functional/job_invocations_controller_test.rb +71 -0
- data/test/support/remote_execution_helper.rb +5 -0
- data/test/unit/actions/run_host_job_test.rb +3 -3
- data/test/unit/actions/run_hosts_job_test.rb +3 -2
- data/test/unit/job_invocation_composer_test.rb +5 -5
- data/test/unit/remote_execution_provider_test.rb +6 -6
- data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +2 -0
- data/webpack/__mocks__/foremanReact/components/SearchBar.js +2 -0
- data/webpack/__mocks__/foremanReact/constants.js +21 -0
- data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +2 -0
- data/webpack/__mocks__/foremanReact/redux/middlewares/IntervalMiddleware/IntervalSelectors.js +1 -0
- data/webpack/react_app/components/TargetingHosts/TargetingHosts.js +25 -15
- data/webpack/react_app/components/TargetingHosts/TargetingHostsHelpers.js +10 -0
- data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.js +66 -0
- data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.scss +6 -0
- data/webpack/react_app/components/TargetingHosts/TargetingHostsSelectors.js +10 -2
- data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsPage.test.js +9 -0
- data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +26 -0
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHosts.test.js.snap +16 -1
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +68 -0
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +11 -0
- data/webpack/react_app/components/TargetingHosts/__tests__/fixtures.js +35 -19
- data/webpack/react_app/components/TargetingHosts/index.js +73 -13
- metadata +30 -7
- data/webpack/react_app/components/TargetingHosts/TargetingHostsActions.js +0 -8
@@ -1,5 +1,5 @@
|
|
1
1
|
class RemoteExecutionFeature < ApplicationRecord
|
2
|
-
VALID_OPTIONS = [:provided_inputs, :description, :host_action_button, :notification_builder].freeze
|
2
|
+
VALID_OPTIONS = [:provided_inputs, :description, :host_action_button, :notification_builder, :proxy_selector_override].freeze
|
3
3
|
validates :label, :name, :presence => true, :uniqueness => true
|
4
4
|
|
5
5
|
belongs_to :job_template
|
@@ -24,8 +24,10 @@ class RemoteExecutionFeature < ApplicationRecord
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def self.register(label, name, options = {})
|
27
|
+
pending_migrations = ::Foreman::Plugin.registered_plugins[:foreman_remote_execution]&.pending_migrations
|
27
28
|
begin
|
28
|
-
|
29
|
+
# Let's not try to register features if rex is not registered as a plugin
|
30
|
+
return false if pending_migrations || pending_migrations.nil?
|
29
31
|
rescue ActiveRecord::NoDatabaseError => e
|
30
32
|
# just ignore the problem if DB does not exist yet (rake db:create call)
|
31
33
|
return false
|
@@ -41,6 +43,7 @@ class RemoteExecutionFeature < ApplicationRecord
|
|
41
43
|
:provided_input_names => options[:provided_inputs],
|
42
44
|
:description => options[:description],
|
43
45
|
:host_action_button => options[:host_action_button],
|
46
|
+
:proxy_selector_override => options[:proxy_selector_override],
|
44
47
|
:notification_builder => builder }
|
45
48
|
# in case DB does not have the attribute created yet but plugin initializer registers the feature, we need to skip this attribute
|
46
49
|
attrs = [ :host_action_button, :notification_builder ]
|
@@ -57,8 +57,8 @@ class RemoteExecutionProvider
|
|
57
57
|
[true, 'true', 'True', 'TRUE', '1'].include?(setting)
|
58
58
|
end
|
59
59
|
|
60
|
-
def
|
61
|
-
host_setting(host, :
|
60
|
+
def effective_user_password(host)
|
61
|
+
host_setting(host, :remote_execution_effective_user_password)
|
62
62
|
end
|
63
63
|
|
64
64
|
def effective_interfaces(host)
|
@@ -101,7 +101,12 @@ class RemoteExecutionProvider
|
|
101
101
|
|
102
102
|
# Return a specific proxy selector to use for running a given template
|
103
103
|
# Returns either nil to use the default selector or an instance of a (sub)class of ::ForemanTasks::ProxySelector
|
104
|
-
def required_proxy_selector_for(
|
104
|
+
def required_proxy_selector_for(template)
|
105
|
+
if template.remote_execution_features
|
106
|
+
.where(:proxy_selector_override => ::RemoteExecutionProxySelector::INTERNAL_PROXY)
|
107
|
+
.any?
|
108
|
+
::DefaultProxyProxySelector.new
|
109
|
+
end
|
105
110
|
end
|
106
111
|
end
|
107
112
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class Setting::RemoteExecution < Setting
|
2
2
|
|
3
|
-
::Setting::BLANK_ATTRS.concat %w{remote_execution_ssh_password remote_execution_ssh_key_passphrase remote_execution_sudo_password remote_execution_cockpit_url remote_execution_form_job_template}
|
3
|
+
::Setting::BLANK_ATTRS.concat %w{remote_execution_ssh_password remote_execution_ssh_key_passphrase remote_execution_sudo_password remote_execution_effective_user_password remote_execution_cockpit_url remote_execution_form_job_template}
|
4
4
|
|
5
5
|
def self.default_settings
|
6
6
|
[
|
@@ -27,7 +27,7 @@ class Setting::RemoteExecution < Setting
|
|
27
27
|
N_('Effective User Method'),
|
28
28
|
nil,
|
29
29
|
{ :collection => proc { Hash[SSHExecutionProvider::EFFECTIVE_USER_METHODS.map { |method| [method, method] }] } }),
|
30
|
-
self.set('
|
30
|
+
self.set('remote_execution_effective_user_password', N_("Effective user password"), '', N_("Effective user password"), nil, {:encrypted => true}),
|
31
31
|
self.set('remote_execution_sync_templates',
|
32
32
|
N_('Whether we should sync templates from disk when running db:seed.'),
|
33
33
|
true,
|
@@ -32,7 +32,7 @@ class SSHExecutionProvider < RemoteExecutionProvider
|
|
32
32
|
{
|
33
33
|
:ssh_password => ssh_password(host),
|
34
34
|
:key_passphrase => ssh_key_passphrase(host),
|
35
|
-
:
|
35
|
+
:effective_user_password => effective_user_password(host),
|
36
36
|
}
|
37
37
|
end
|
38
38
|
|
@@ -0,0 +1 @@
|
|
1
|
+
attributes :execution
|
@@ -8,6 +8,7 @@ node do |invocation|
|
|
8
8
|
:failed => invocation_count(invocation, :output_key => :failed_count),
|
9
9
|
:pending => invocation_count(invocation, :output_key => :pending_count),
|
10
10
|
:total => invocation_count(invocation, :output_key => :total_count),
|
11
|
+
:missing => invocation.missing_hosts_count,
|
11
12
|
}
|
12
13
|
end
|
13
14
|
|
@@ -19,7 +19,7 @@ child :targeting do
|
|
19
19
|
attributes :bookmark_id, :search_query, :targeting_type, :user_id, :status, :status_label,
|
20
20
|
:randomized_ordering
|
21
21
|
|
22
|
-
child @hosts do
|
22
|
+
child @hosts => :hosts do
|
23
23
|
extends 'api/v2/hosts/base'
|
24
24
|
|
25
25
|
if params[:host_status] == 'true'
|
@@ -35,7 +35,7 @@ child :task do
|
|
35
35
|
end
|
36
36
|
|
37
37
|
child @template_invocations do
|
38
|
-
attributes :template_id, :template_name
|
38
|
+
attributes :template_id, :template_name, :host_id
|
39
39
|
child :input_values do
|
40
40
|
attributes :template_input_name, :template_input_id
|
41
41
|
node :value do |iv|
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<div class='form-group'>
|
2
|
+
<label class='col-md-2 control-label'>
|
3
|
+
<%= _('Remote Execution Interface') %>
|
4
|
+
<% help = _('Identifier of the Host interface for Remote execution') %>
|
5
|
+
<a rel="popover" data-content="<%= help %>" data-trigger="focus" data-container="body" data-html="true" tabindex="-1">
|
6
|
+
<span class="pficon pficon-info "></span>
|
7
|
+
</a>
|
8
|
+
</label>
|
9
|
+
<div class='col-md-4'>
|
10
|
+
<%= text_field_tag 'remote_execution_interface', params[:remote_execution_interface], class: 'form-control' %>
|
11
|
+
</div>
|
12
|
+
</div>
|
@@ -95,7 +95,7 @@
|
|
95
95
|
<div class="advanced hidden">
|
96
96
|
<%= password_f f, :password, :placeholder => '*****', :label => _('Password'), :label_help => N_('Password is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %>
|
97
97
|
<%= password_f f, :key_passphrase, :placeholder => '*****', :label => _('Private key passphrase'), :label_help => N_('Key passhprase is only applicable for SSH provider. Other providers ignore this field. <br> Passphrase is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %>
|
98
|
-
<%= password_f f, :
|
98
|
+
<%= password_f f, :effective_user_password, :placeholder => '*****', :label => _('Effective user password'), :label_help => N_('Effective user password is only applicable for SSH provider. Other providers ignore this field. <br> Password is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %>
|
99
99
|
</div>
|
100
100
|
|
101
101
|
<div class="advanced hidden">
|
@@ -1,24 +1,5 @@
|
|
1
1
|
<% if job_invocation.resolved? %>
|
2
|
-
<%=
|
3
|
-
<div class="row">
|
4
|
-
<div class="title_filter col-md-6">
|
5
|
-
<div class="input-group">
|
6
|
-
<%= autocomplete_f(f, :search, value: params[:search].try(:squeeze, " "), placeholder: _("Filter") + ' ...', path: hosts_path, only_input: true) %>
|
7
|
-
<span class="input-group-btn">
|
8
|
-
<button class="btn btn-default" type="submit">
|
9
|
-
<%= icon_text('search', content_tag(:span, _('Search'), :class => 'hidden-xs', :kind => 'fa')) %>
|
10
|
-
</button>
|
11
|
-
</span>
|
12
|
-
</div>
|
13
|
-
</div>
|
14
|
-
</div>
|
15
|
-
<% end %>
|
16
|
-
<br>
|
17
|
-
|
18
|
-
<div id="targeting_hosts">
|
19
|
-
<%= mount_react_component('TargetingHosts', '#targeting_hosts') %>
|
20
|
-
</div>
|
21
|
-
<%= will_paginate_with_info @hosts, :container => true %>
|
2
|
+
<%= react_component('TargetingHosts' ) %>
|
22
3
|
<% else %>
|
23
4
|
<div class="alert alert-warning">
|
24
5
|
<%=
|
@@ -9,7 +9,19 @@
|
|
9
9
|
<% template_invocations.each do |template_invocation| %>
|
10
10
|
<%= minicard('user', template_invocation.effective_user || Setting[:remote_execution_effective_user],
|
11
11
|
template_invocation.template.name + ' ' + _('effective user')) %>
|
12
|
-
|
12
|
+
<div class="row">
|
13
|
+
<% missing = job_invocation.missing_hosts_count %>
|
14
|
+
<% size = missing.zero? ? 12 : 6 %>
|
15
|
+
<div class="col-xs-12 col-sm-<%= size %> col-md-<%= size %>" >
|
16
|
+
<%= minicard('cluster', job_invocation.total_hosts_count, _('Total hosts')) %>
|
17
|
+
</div>
|
18
|
+
<% unless missing.zero? %>
|
19
|
+
<div class="col-xs-12 col-sm-6 col-md-6" >
|
20
|
+
<%= minicard('warning-triangle-o', missing, _('Hosts gone missing'),
|
21
|
+
:tooltip => _('This can happen if the host is removed or moved to another organization or location after the job was started')) %>
|
22
|
+
</div>
|
23
|
+
<% end %>
|
24
|
+
</div>
|
13
25
|
<% if template_invocation.input_values.present? %>
|
14
26
|
<%= render :partial => 'card_user_input', :locals => { :template_invocation => template_invocation } %>
|
15
27
|
<% end %>
|
@@ -2,6 +2,9 @@
|
|
2
2
|
<% stylesheet 'foreman_remote_execution/foreman_remote_execution' %>
|
3
3
|
<% javascript 'charts', 'foreman_remote_execution/template_invocation' %>
|
4
4
|
<% javascript *webpack_asset_paths('foreman_remote_execution', :extension => 'js') %>
|
5
|
+
<% content_for(:stylesheets) do %>
|
6
|
+
<%= webpacked_plugins_css_for :foreman_remote_execution %>
|
7
|
+
<% end %>
|
5
8
|
|
6
9
|
<%= breadcrumbs name_field: 'description' %>
|
7
10
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<% output_line_set['output'].gsub("\r\n", "\n").sub(/\n\Z/, '').split("\n", -1).each do |line| %>
|
2
2
|
<%= content_tag :div, :class => 'line ' + output_line_set['output_type'], :data => { :timestamp => output_line_set['timestamp'] } do %>
|
3
3
|
|
4
|
-
<%= content_tag(:span, (@line_counter += 1).to_s.rjust(4).gsub(' ', ' ').html_safe + ':', :class => 'counter', :title => (output_line_set['timestamp'] && Time.at(output_line_set['timestamp']))) %>
|
4
|
+
<%= content_tag(:span, (@line_counter += 1).to_s.rjust(4).gsub(' ', ' ').html_safe + ':', :class => 'counter', :title => (output_line_set['timestamp'] && Time.at(output_line_set['timestamp'].to_f))) %>
|
5
5
|
<%= content_tag(:div, colorize_line(line.gsub(JobInvocationOutputHelper::COLOR_PATTERN, '').empty? ? "#{line}\n" : line).html_safe, :class => 'content') %>
|
6
6
|
<% end %>
|
7
7
|
<% end %>
|
@@ -98,6 +98,7 @@ handle_zypp_res_codes () {
|
|
98
98
|
end
|
99
99
|
-%>
|
100
100
|
[ -x "$(command -v subscription-manager)" ] && subscription-manager refresh
|
101
|
+
export DEBIAN_FRONTEND=noninteractive
|
101
102
|
apt-get -y update
|
102
103
|
apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y <%= action %> <%= input("package") %>
|
103
104
|
<% elsif package_manager == 'zypper' -%>
|
data/config/routes.rb
CHANGED
@@ -0,0 +1,34 @@
|
|
1
|
+
class RenameSudoPasswordToEffectiveUserPassword < ActiveRecord::Migration[6.0]
|
2
|
+
def up
|
3
|
+
rename_column :job_invocations, :sudo_password, :effective_user_password
|
4
|
+
|
5
|
+
Parameter.where(name: 'remote_execution_sudo_password').each do |parameter|
|
6
|
+
record = Parameter.find_by(type: parameter.type, reference_id: parameter.reference_id, name: "remote_execution_effective_user_password")
|
7
|
+
if record.nil?
|
8
|
+
parameter.update(name: "remote_execution_effective_user_password")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
return unless (password = Setting.find_by(:name => 'remote_execution_sudo_password').try(:value))
|
13
|
+
|
14
|
+
Setting.find_by(:name => 'remote_execution_effective_user_password').update(value: password)
|
15
|
+
|
16
|
+
Setting.find_by(:name => 'remote_execution_sudo_password').delete
|
17
|
+
end
|
18
|
+
|
19
|
+
def down
|
20
|
+
rename_column :job_invocations, :effective_user_password, :sudo_password
|
21
|
+
|
22
|
+
Parameter.where(name: 'remote_execution_effective_user_password').each do |parameter|
|
23
|
+
record = Parameter.find_by(type: parameter.type, reference_id: parameter.reference_id, name: "remote_execution_sudo_password")
|
24
|
+
if record.nil?
|
25
|
+
parameter.update(name: "remote_execution_sudo_password")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
return unless (password = Setting.find_by(:name => 'remote_execution_effective_user_password').try(:value))
|
30
|
+
|
31
|
+
Setting.create!(name: 'remote_execution_sudo_password', value: password, description: 'Sudo password', category: 'Setting::RemoteExecution', settings_type: 'string', full_name: 'Sudo password',encrypted: true, default: nil)
|
32
|
+
Setting.find_by(:name => 'remote_execution_effective_user_password').delete
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
view_permission = Permission.find_by(name: "view_job_invocations", resource_type: 'JobInvocation')
|
2
|
+
default_role = Role.default
|
3
|
+
|
4
|
+
# the view_permissions can be nil in tests: skipping in that case
|
5
|
+
if view_permission && !default_role.permissions.include?(view_permission)
|
6
|
+
default_role.filters.create(:search => 'user = current_user') do |filter|
|
7
|
+
filter.filterings.build { |f| f.permission = view_permission }
|
8
|
+
end
|
9
|
+
end
|
@@ -32,6 +32,15 @@ module ForemanRemoteExecution
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
# A workaround for https://projects.theforeman.org/issues/30685
|
36
|
+
initializer 'foreman_remote_execution.rails_loading_workaround' do
|
37
|
+
# Without this, in production environment the module gets prepended too
|
38
|
+
# late and the extensions do not get applied
|
39
|
+
# TODO: Remove this and from config.to_prepare once there is an extension
|
40
|
+
# point in Foreman
|
41
|
+
ProvisioningTemplatesHelper.prepend ForemanRemoteExecution::JobTemplatesExtensions
|
42
|
+
end
|
43
|
+
|
35
44
|
initializer 'foreman_remote_execution.apipie' do
|
36
45
|
Apipie.configuration.checksum_path += ['/api/']
|
37
46
|
end
|
@@ -44,9 +53,12 @@ module ForemanRemoteExecution
|
|
44
53
|
|
45
54
|
initializer 'foreman_remote_execution.register_plugin', before: :finisher_hook do |_app|
|
46
55
|
Foreman::Plugin.register :foreman_remote_execution do
|
47
|
-
requires_foreman '>=
|
56
|
+
requires_foreman '>= 2.2'
|
48
57
|
|
49
58
|
apipie_documented_controllers ["#{ForemanRemoteExecution::Engine.root}/app/controllers/api/v2/*.rb"]
|
59
|
+
ApipieDSL.configuration.dsl_classes_matchers += [
|
60
|
+
"#{ForemanRemoteExecution::Engine.root}/app/lib/foreman_remote_execution/renderer/**/*.rb",
|
61
|
+
]
|
50
62
|
|
51
63
|
automatic_assets(false)
|
52
64
|
precompile_assets(*assets_to_precompile)
|
@@ -71,7 +83,7 @@ module ForemanRemoteExecution
|
|
71
83
|
permission :create_job_invocations, { :job_invocations => [:new, :create, :refresh, :rerun, :preview_hosts],
|
72
84
|
'api/v2/job_invocations' => [:create, :rerun] }, :resource_type => 'JobInvocation'
|
73
85
|
permission :view_job_invocations, { :job_invocations => [:index, :chart, :show, :auto_complete_search], :template_invocations => [:show],
|
74
|
-
'api/v2/job_invocations' => [:index, :show, :output, :raw_output] }, :resource_type => 'JobInvocation'
|
86
|
+
'api/v2/job_invocations' => [:index, :show, :output, :raw_output, :outputs] }, :resource_type => 'JobInvocation'
|
75
87
|
permission :view_template_invocations, { :template_invocations => [:show],
|
76
88
|
'api/v2/template_invocations' => [:template_invocations] }, :resource_type => 'TemplateInvocation'
|
77
89
|
permission :create_template_invocations, {}, :resource_type => 'TemplateInvocation'
|
@@ -135,7 +147,16 @@ module ForemanRemoteExecution
|
|
135
147
|
end
|
136
148
|
|
137
149
|
extend_rabl_template 'api/v2/smart_proxies/main', 'api/v2/smart_proxies/pubkey'
|
150
|
+
extend_rabl_template 'api/v2/interfaces/main', 'api/v2/interfaces/execution_flag'
|
151
|
+
extend_rabl_template 'api/v2/subnets/show', 'api/v2/subnets/remote_execution_proxies'
|
152
|
+
parameter_filter ::Subnet, :remote_execution_proxy_ids
|
138
153
|
describe_host { overview_buttons_provider :host_overview_buttons }
|
154
|
+
|
155
|
+
# Extend Registration module
|
156
|
+
extend_allowed_registration_vars :remote_execution_interface
|
157
|
+
extend_page 'registration/_form' do |cx|
|
158
|
+
cx.add_pagelet :global_registration, name: N_('Remote Execution'), partial: 'api/v2/registration/form', priority: 100, id: 'remote_execution_interface'
|
159
|
+
end
|
139
160
|
end
|
140
161
|
end
|
141
162
|
|
@@ -181,6 +202,7 @@ module ForemanRemoteExecution
|
|
181
202
|
SmartProxy.prepend ForemanRemoteExecution::SmartProxyExtensions
|
182
203
|
Subnet.include ForemanRemoteExecution::SubnetExtensions
|
183
204
|
|
205
|
+
::Api::V2::InterfacesController.include Api::V2::InterfacesControllerExtensions
|
184
206
|
# We need to explicitly force to load the Task model due to Rails loader
|
185
207
|
# having issues with resolving it to Rake::Task otherwise
|
186
208
|
require_dependency 'foreman_tasks/task'
|
@@ -189,6 +211,10 @@ module ForemanRemoteExecution
|
|
189
211
|
RemoteExecutionProvider.register(:SSH, SSHExecutionProvider)
|
190
212
|
|
191
213
|
ForemanRemoteExecution.register_rex_feature
|
214
|
+
|
215
|
+
::Api::V2::SubnetsController.include ::ForemanRemoteExecution::Concerns::Api::V2::SubnetsControllerExtensions
|
216
|
+
::Api::V2::RegistrationController.prepend ::ForemanRemoteExecution::Concerns::Api::V2::RegistrationControllerExtensions
|
217
|
+
::Api::V2::RegistrationController.include ::ForemanRemoteExecution::Concerns::Api::V2::RegistrationControllerExtensions::ApipieExtensions
|
192
218
|
end
|
193
219
|
|
194
220
|
initializer 'foreman_remote_execution.register_gettext', after: :load_config_initializers do |_app|
|
data/package.json
CHANGED
@@ -21,16 +21,16 @@
|
|
21
21
|
},
|
22
22
|
"devDependencies": {
|
23
23
|
"@babel/core": "^7.7.0",
|
24
|
-
"@theforeman/builder": "^4.
|
25
|
-
"@theforeman/eslint-plugin-foreman": "^4.
|
26
|
-
"@theforeman/stories": "^4.
|
27
|
-
"@theforeman/test": "^4.
|
28
|
-
"@theforeman/vendor-dev": "^4.
|
24
|
+
"@theforeman/builder": "^4.14.0",
|
25
|
+
"@theforeman/eslint-plugin-foreman": "^4.14.0",
|
26
|
+
"@theforeman/stories": "^4.14.0",
|
27
|
+
"@theforeman/test": "^4.14.0",
|
28
|
+
"@theforeman/vendor-dev": "^4.14.0",
|
29
29
|
"babel-eslint": "^10.0.0",
|
30
30
|
"eslint": "^6.8.0",
|
31
31
|
"prettier": "^1.19.1"
|
32
32
|
},
|
33
33
|
"peerDependencies": {
|
34
|
-
"@theforeman/vendor": ">= 4.
|
34
|
+
"@theforeman/vendor": ">= 4.14.0"
|
35
35
|
}
|
36
36
|
}
|
@@ -38,13 +38,14 @@ module Api
|
|
38
38
|
|
39
39
|
test 'should see only permitted hosts' do
|
40
40
|
@user = FactoryBot.create(:user, admin: false)
|
41
|
+
@invocation.task.update(user: @user)
|
41
42
|
setup_user('view', 'job_invocations', nil, @user)
|
42
43
|
setup_user('view', 'hosts', 'name ~ nope.example.com', @user)
|
43
44
|
|
44
|
-
get :show, params: { :id => @invocation.id }, session:
|
45
|
+
get :show, params: { :id => @invocation.id }, session: prepare_user(@user)
|
45
46
|
assert_response :success
|
46
47
|
response = ActiveSupport::JSON.decode(@response.body)
|
47
|
-
|
48
|
+
assert_equal response['targeting']['hosts'], []
|
48
49
|
end
|
49
50
|
end
|
50
51
|
|
@@ -159,6 +160,25 @@ module Api
|
|
159
160
|
end
|
160
161
|
end
|
161
162
|
|
163
|
+
describe '#outputs' do
|
164
|
+
test 'should provide outputs for hosts in the job' do
|
165
|
+
get :outputs, params: { :id => @invocation.id }
|
166
|
+
result = ActiveSupport::JSON.decode(@response.body)
|
167
|
+
host_output = result['outputs'].first
|
168
|
+
assert_equal host_output['host_id'], @invocation.targeting.host_ids.first
|
169
|
+
assert_equal host_output['refresh'], true
|
170
|
+
assert_equal host_output['output'], []
|
171
|
+
assert_response :success
|
172
|
+
end
|
173
|
+
|
174
|
+
test 'should provide outputs for hosts in the job matching a search query' do
|
175
|
+
get :outputs, params: { :id => @invocation.id, :search_query => "name = definitely_not_in_the_job" }
|
176
|
+
result = ActiveSupport::JSON.decode(@response.body)
|
177
|
+
assert_equal result['outputs'], []
|
178
|
+
assert_response :success
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
162
182
|
describe 'raw output' do
|
163
183
|
let(:fake_output) do
|
164
184
|
(1..5).map do |i|
|
@@ -298,6 +318,68 @@ module Api
|
|
298
318
|
post :rerun, params: { :id => @invocation.id }
|
299
319
|
assert_response 404
|
300
320
|
end
|
321
|
+
|
322
|
+
describe 'restricted access' do
|
323
|
+
setup do
|
324
|
+
@admin = FactoryBot.create(:user, mail: 'admin@test.foreman.com', admin: true)
|
325
|
+
@user = FactoryBot.create(:user, mail: 'user@test.foreman.com', admin: false)
|
326
|
+
@invocation = FactoryBot.create(:job_invocation, :with_template, :with_task, :with_unplanned_host)
|
327
|
+
@invocation2 = FactoryBot.create(:job_invocation, :with_template, :with_task, :with_unplanned_host)
|
328
|
+
|
329
|
+
@invocation.task.update(user: @admin)
|
330
|
+
@invocation2.task.update(user: @user)
|
331
|
+
|
332
|
+
setup_user 'view', 'hosts', nil, @user
|
333
|
+
setup_user 'view', 'job_invocations', 'user = current_user', @user
|
334
|
+
setup_user 'create', 'job_invocations', 'user = current_user', @user
|
335
|
+
setup_user 'cancel', 'job_invocations', 'user = current_user', @user
|
336
|
+
end
|
337
|
+
|
338
|
+
let(:host) { @invocation.targeting.hosts.first }
|
339
|
+
let(:host2) { @invocation2.targeting.hosts.first }
|
340
|
+
|
341
|
+
context 'without user filter' do
|
342
|
+
test '#index' do
|
343
|
+
get :index, session: prepare_user(@admin)
|
344
|
+
assert_response :success
|
345
|
+
assert JSON.parse(@response.body)['results'].size >= 2
|
346
|
+
end
|
347
|
+
|
348
|
+
test '#show' do
|
349
|
+
get :show, params: { id: @invocation2.id }, session: prepare_user(@admin)
|
350
|
+
assert_response :success
|
351
|
+
end
|
352
|
+
|
353
|
+
test '#output' do
|
354
|
+
get :output, params: { job_invocation_id: @invocation2.id, host_id: host2.id }, session: prepare_user(@admin)
|
355
|
+
assert_response :success
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
context 'with user filter' do
|
360
|
+
test '#index' do
|
361
|
+
get :index, session: prepare_user(@user)
|
362
|
+
assert_response :success
|
363
|
+
assert_equal 1, JSON.parse(@response.body)['results'].size
|
364
|
+
end
|
365
|
+
|
366
|
+
test '#show' do
|
367
|
+
get :show, params: { id: @invocation.id }, session: prepare_user(@user)
|
368
|
+
assert_response :not_found
|
369
|
+
end
|
370
|
+
|
371
|
+
test '#output' do
|
372
|
+
get :output, params: { job_invocation_id: @invocation.id, host_id: host.id }, session: prepare_user(@user)
|
373
|
+
assert_response :not_found
|
374
|
+
assert_includes @response.body, 'Job invocation not found'
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
def prepare_user(user)
|
380
|
+
User.current = user
|
381
|
+
set_session_user(user)
|
382
|
+
end
|
301
383
|
end
|
302
384
|
end
|
303
385
|
end
|