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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -1
  3. data/app/controllers/api/v2/job_invocations_controller.rb +20 -2
  4. data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_controller_extensions.rb +26 -0
  5. data/app/controllers/foreman_remote_execution/concerns/api/v2/subnets_controller_extensions.rb +21 -0
  6. data/app/controllers/job_invocations_controller.rb +22 -8
  7. data/app/controllers/job_templates_controller.rb +1 -1
  8. data/app/helpers/job_invocations_helper.rb +3 -2
  9. data/app/lib/actions/remote_execution/run_host_job.rb +1 -1
  10. data/app/lib/actions/remote_execution/run_hosts_job.rb +12 -3
  11. data/app/lib/foreman_remote_execution/renderer/scope/input.rb +35 -0
  12. data/app/models/concerns/api/v2/interfaces_controller_extensions.rb +13 -0
  13. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +38 -14
  14. data/app/models/job_invocation.rb +12 -4
  15. data/app/models/job_invocation_composer.rb +2 -2
  16. data/app/models/remote_execution_feature.rb +5 -2
  17. data/app/models/remote_execution_provider.rb +8 -3
  18. data/app/models/setting/remote_execution.rb +2 -2
  19. data/app/models/ssh_execution_provider.rb +1 -1
  20. data/app/services/remote_execution_proxy_selector.rb +3 -0
  21. data/app/views/api/v2/interfaces/execution_flag.json.rabl +1 -0
  22. data/app/views/api/v2/job_invocations/base.json.rabl +1 -0
  23. data/app/views/api/v2/job_invocations/main.json.rabl +2 -2
  24. data/app/views/api/v2/registration/_form.html.erb +12 -0
  25. data/app/views/api/v2/subnets/remote_execution_proxies.json.rabl +3 -0
  26. data/app/views/job_invocations/_form.html.erb +1 -1
  27. data/app/views/job_invocations/_tab_hosts.html.erb +1 -20
  28. data/app/views/job_invocations/_tab_overview.html.erb +13 -1
  29. data/app/views/job_invocations/show.html.erb +3 -0
  30. data/app/views/job_invocations/show.json.erb +2 -1
  31. data/app/views/template_invocations/_output_line_set.html.erb +1 -1
  32. data/app/views/templates/ssh/package_action.erb +1 -0
  33. data/config/routes.rb +1 -0
  34. data/db/migrate/20200623073022_rename_sudo_password_to_effective_user_password.rb +34 -0
  35. data/db/migrate/20200820122057_add_proxy_selector_override_to_remote_execution_feature.rb +5 -0
  36. data/db/seeds.d/20-permissions.rb +9 -0
  37. data/lib/foreman_remote_execution/engine.rb +28 -2
  38. data/lib/foreman_remote_execution/version.rb +1 -1
  39. data/package.json +6 -6
  40. data/test/functional/api/v2/job_invocations_controller_test.rb +84 -2
  41. data/test/functional/api/v2/registration_controller_test.rb +82 -0
  42. data/test/functional/job_invocations_controller_test.rb +71 -0
  43. data/test/support/remote_execution_helper.rb +5 -0
  44. data/test/unit/actions/run_host_job_test.rb +3 -3
  45. data/test/unit/actions/run_hosts_job_test.rb +3 -2
  46. data/test/unit/job_invocation_composer_test.rb +5 -5
  47. data/test/unit/remote_execution_provider_test.rb +6 -6
  48. data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +2 -0
  49. data/webpack/__mocks__/foremanReact/components/SearchBar.js +2 -0
  50. data/webpack/__mocks__/foremanReact/constants.js +21 -0
  51. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +2 -0
  52. data/webpack/__mocks__/foremanReact/redux/middlewares/IntervalMiddleware/IntervalSelectors.js +1 -0
  53. data/webpack/react_app/components/TargetingHosts/TargetingHosts.js +25 -15
  54. data/webpack/react_app/components/TargetingHosts/TargetingHostsHelpers.js +10 -0
  55. data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.js +66 -0
  56. data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.scss +6 -0
  57. data/webpack/react_app/components/TargetingHosts/TargetingHostsSelectors.js +10 -2
  58. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsPage.test.js +9 -0
  59. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +26 -0
  60. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHosts.test.js.snap +16 -1
  61. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +68 -0
  62. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +11 -0
  63. data/webpack/react_app/components/TargetingHosts/__tests__/fixtures.js +35 -19
  64. data/webpack/react_app/components/TargetingHosts/index.js +73 -13
  65. metadata +30 -7
  66. 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
- return false unless RemoteExecutionFeature.table_exists?
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 sudo_password(host)
61
- host_setting(host, :remote_execution_sudo_password)
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(_template)
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('remote_execution_sudo_password', N_("Sudo password"), '', N_("Sudo password"), nil, {:encrypted => true}),
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
- :sudo_password => sudo_password(host),
35
+ :effective_user_password => effective_user_password(host),
36
36
  }
37
37
  end
38
38
 
@@ -1,4 +1,7 @@
1
1
  class RemoteExecutionProxySelector < ::ForemanTasks::ProxySelector
2
+
3
+ INTERNAL_PROXY = 'internal'.freeze
4
+
2
5
  def available_proxies(host, provider)
3
6
  host.remote_execution_proxies(provider)
4
7
  end
@@ -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>
@@ -0,0 +1,3 @@
1
+ child :remote_execution_proxies => :remote_execution_proxies do
2
+ attributes :name, :id
3
+ end
@@ -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, :sudo_password, :placeholder => '*****', :label => _('Sudo password'), :label_help => N_('Sudo 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.') %>
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
- <%= form_with url: job_invocation_path(job_invocation), method: "get", id: "search-form" do |f| %>
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
- <%= minicard('cluster', job_invocation.total_hosts_count, _('Total hosts')) %>
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,4 +1,5 @@
1
1
  {
2
2
  "autoRefresh": "<%= @auto_refresh %>",
3
- "hosts": <%= targeting_hosts(@job_invocation, @hosts).to_json.html_safe %>
3
+ "hosts": <%= targeting_hosts(@job_invocation, @hosts).to_json.html_safe %>,
4
+ "total_hosts": <%= @total_hosts %>
4
5
  }
@@ -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(' ', '&nbsp;').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(' ', '&nbsp;').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' -%>
@@ -55,6 +55,7 @@ Rails.application.routes.draw do
55
55
  post 'cancel'
56
56
  post 'rerun'
57
57
  get 'template_invocations', :to => 'template_invocations#template_invocations'
58
+ get 'outputs'
58
59
  end
59
60
  end
60
61
 
@@ -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,5 @@
1
+ class AddProxySelectorOverrideToRemoteExecutionFeature < ActiveRecord::Migration[4.2]
2
+ def change
3
+ add_column :remote_execution_features, :proxy_selector_override, :string
4
+ end
5
+ 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 '>= 1.25'
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|
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '3.3.5'.freeze
2
+ VERSION = '4.2.0'.freeze
3
3
  end
@@ -21,16 +21,16 @@
21
21
  },
22
22
  "devDependencies": {
23
23
  "@babel/core": "^7.7.0",
24
- "@theforeman/builder": "^4.2.1",
25
- "@theforeman/eslint-plugin-foreman": "^4.2.1",
26
- "@theforeman/stories": "^4.2.1",
27
- "@theforeman/test": "^4.2.1",
28
- "@theforeman/vendor-dev": "^4.2.1",
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.2.1"
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: set_session_user(@user)
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
- assert_empty response['targeting']['hosts']
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