foreman_remote_execution 4.0.0 → 4.2.3

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +20 -2
  3. data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_controller_extensions.rb +26 -0
  4. data/app/controllers/foreman_remote_execution/concerns/api/v2/subnets_controller_extensions.rb +21 -0
  5. data/app/controllers/job_templates_controller.rb +1 -1
  6. data/app/helpers/job_invocations_helper.rb +3 -2
  7. data/app/lib/actions/remote_execution/run_hosts_job.rb +11 -2
  8. data/app/models/concerns/api/v2/interfaces_controller_extensions.rb +13 -0
  9. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +38 -14
  10. data/app/models/foreign_input_set.rb +1 -1
  11. data/app/models/job_invocation.rb +6 -1
  12. data/app/models/job_invocation_composer.rb +1 -1
  13. data/app/models/remote_execution_feature.rb +5 -2
  14. data/app/models/remote_execution_provider.rb +6 -1
  15. data/app/services/remote_execution_proxy_selector.rb +3 -0
  16. data/app/views/api/v2/interfaces/execution_flag.json.rabl +1 -0
  17. data/app/views/api/v2/job_invocations/base.json.rabl +1 -0
  18. data/app/views/api/v2/job_invocations/main.json.rabl +1 -1
  19. data/app/views/api/v2/registration/_form.html.erb +12 -0
  20. data/app/views/api/v2/subnets/remote_execution_proxies.json.rabl +3 -0
  21. data/app/views/job_invocations/_tab_overview.html.erb +13 -1
  22. data/app/views/template_invocations/_output_line_set.html.erb +1 -1
  23. data/app/views/template_invocations/show.html.erb +30 -23
  24. data/app/views/templates/ssh/package_action.erb +1 -0
  25. data/config/routes.rb +2 -0
  26. data/db/migrate/20200820122057_add_proxy_selector_override_to_remote_execution_feature.rb +5 -0
  27. data/lib/foreman_remote_execution/engine.rb +24 -1
  28. data/lib/foreman_remote_execution/version.rb +1 -1
  29. data/package.json +6 -6
  30. data/test/functional/api/v2/job_invocations_controller_test.rb +29 -0
  31. data/test/functional/api/v2/registration_controller_test.rb +82 -0
  32. data/test/unit/actions/run_hosts_job_test.rb +2 -1
  33. data/webpack/react_app/components/TargetingHosts/TargetingHosts.js +5 -1
  34. data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.js +5 -1
  35. metadata +15 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93860edb8974a0691383b3a199a1a2d079d10401d7780b99aa6275b75125eb58
4
- data.tar.gz: d333f8eb85a42b36694dee932447d4364b57ed2c095ad5a055d1e7adf69a9866
3
+ metadata.gz: 6a9f5c20b58b5911c612164cc806d63b3096c81506b18cd11423fb4b073bdec4
4
+ data.tar.gz: 2c8615b8ef06bf5d5adc3e62ab2a36906714ebe9e99adbf13dfb3eaa742819d6
5
5
  SHA512:
6
- metadata.gz: 70c300e719d6587639719a4d95aa23da2d3f3a16d51692392c789b70cd381b31320789db0f45b57c129e4b90d36bd1a8ddadd29db4f38f2d9e40a39219b1d130
7
- data.tar.gz: 1feab0c5cfd3fee6054a004eba812b7257df80a2dce50aa200d15dd20797f24f8a614d802668ac6daf043c189b7caa8e8d5f0d4a997726764c80c91173d9437b
6
+ metadata.gz: 549daa0a07ac47a7ed596187fd86943d9e1e517dab1d653af8f454ccee9e2b83fa85b6e2ec188201e5f849978b50b69108a0dbf6a6c67844b909de3b1b1e846f
7
+ data.tar.gz: b6e71fedfb385b05aa488fd65b68c2c242eba84783da007e26a6cf5f5b1b837de7bcbcfeecc72459b4c48cee93513f45948aa14318bbc548a52f159f2ae967cd
@@ -6,7 +6,7 @@ module Api
6
6
 
7
7
  before_action :find_optional_nested_object, :only => %w{output raw_output}
8
8
  before_action :find_host, :only => %w{output raw_output}
9
- before_action :find_resource, :only => %w{show update destroy clone cancel rerun}
9
+ before_action :find_resource, :only => %w{show update destroy clone cancel rerun outputs}
10
10
 
11
11
  wrap_parameters JobInvocation, :include => (JobInvocation.attribute_names + [:ssh])
12
12
 
@@ -137,6 +137,24 @@ module Api
137
137
  end
138
138
  end
139
139
 
140
+ api :GET, '/job_invocations/:id/outputs', N_('Get outputs of hosts in a job')
141
+ param :id, :identifier, :required => true
142
+ param :search_query, :identifier, :required => false
143
+ param :since, String, :required => false
144
+ param :raw, String, :required => false
145
+ def outputs
146
+ hosts = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
147
+ hosts = hosts.search_for(params['search_query']) if params['search_query']
148
+ raw = ActiveRecord::Type::Boolean.new.cast params['raw']
149
+ default_value = raw ? '' : []
150
+ outputs = hosts.map do |host|
151
+ host_output(@job_invocation, host, :default => default_value, :since => params['since'], :raw => raw)
152
+ .merge(host_id: host.id)
153
+ end
154
+
155
+ render :json => { :outputs => outputs }
156
+ end
157
+
140
158
  private
141
159
 
142
160
  def allowed_nested_id
@@ -145,7 +163,7 @@ module Api
145
163
 
146
164
  def action_permission
147
165
  case params[:action]
148
- when 'output', 'raw_output'
166
+ when 'output', 'raw_output', 'outputs'
149
167
  :view
150
168
  when 'cancel'
151
169
  :cancel
@@ -0,0 +1,26 @@
1
+ module ForemanRemoteExecution
2
+ module Concerns
3
+ module Api::V2::RegistrationControllerExtensions
4
+ module ApipieExtensions
5
+ extend Apipie::DSL::Concern
6
+
7
+ update_api(:global, :host) do
8
+ param :remote_execution_interface, String, desc: N_("Identifier of the Host interface for Remote execution")
9
+ end
10
+ end
11
+
12
+ extend ActiveSupport::Concern
13
+
14
+ def host_setup_extension
15
+ remote_execution_interface
16
+ super
17
+ end
18
+
19
+ def remote_execution_interface
20
+ return unless params['remote_execution_interface'].present?
21
+
22
+ @host.set_execution_interface(params['remote_execution_interface'])
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ module ForemanRemoteExecution
2
+ module Concerns
3
+ module Api::V2::SubnetsControllerExtensions
4
+ module ApiPieExtensions
5
+ extend ::Apipie::DSL::Concern
6
+
7
+ update_api(:create, :update) do
8
+ param :subnet, Hash do
9
+ param :remote_execution_proxy_ids, Array, _('List of proxy IDs to be used for remote execution')
10
+ end
11
+ end
12
+ end
13
+
14
+ extend ActiveSupport::Concern
15
+
16
+ included do
17
+ include ApiPieExtensions
18
+ end
19
+ end
20
+ end
21
+ end
@@ -36,7 +36,7 @@ class JobTemplatesController < ::TemplatesController
36
36
 
37
37
  @template = JobTemplate.import_raw(contents, :update => Foreman::Cast.to_bool(params[:imported_template][:overwrite]))
38
38
  if @template&.save
39
- flash[:notice] = _('Job template imported successfully.')
39
+ flash[:success] = _('Job template imported successfully.')
40
40
  redirect_to job_templates_path(:search => "name = \"#{@template.name}\"")
41
41
  else
42
42
  @template ||= JobTemplate.import_raw(contents, :build_new => true)
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal:true
2
2
 
3
3
  module JobInvocationsHelper
4
- def minicard(icon, number, text)
4
+ def minicard(icon, number, text, tooltip: nil)
5
+ tooltip_options = tooltip ? { :'data-original-title' => tooltip, :rel => 'twipsy' } : {}
5
6
  content_tag(:div, :class => 'card-pf card-pf-accented
6
7
  card-pf-aggregate-status card-pf-aggregate-status-mini') do
7
- content_tag(:h2, :class => 'card-pf-title', :style => 'line-height: 1.1') do
8
+ content_tag(:h2, { :class => 'card-pf-title', :style => 'line-height: 1.1' }.merge(tooltip_options)) do
8
9
  icon_text(icon, '', :kind => 'pficon') +
9
10
  content_tag(:span, number, :class =>'card-pf-aggregate-status-count') +
10
11
  text
@@ -57,15 +57,24 @@ module Actions
57
57
  end
58
58
 
59
59
  def job_invocation
60
- @job_invocation ||= JobInvocation.find(input[:job_invocation_id])
60
+ id = input[:job_invocation_id] || input.fetch(:job_invocation, {})[:id]
61
+ @job_invocation ||= JobInvocation.find(id)
61
62
  end
62
63
 
63
64
  def batch(from, size)
64
65
  hosts.offset(from).limit(size)
65
66
  end
66
67
 
68
+ def initiate
69
+ output[:host_count] = total_count
70
+ super
71
+ end
72
+
67
73
  def total_count
68
- hosts.count
74
+ # For compatibility with already existing tasks
75
+ return output[:total_count] unless output.has_key?(:host_count) || task.pending?
76
+
77
+ output[:host_count] || hosts.count
69
78
  end
70
79
 
71
80
  def hosts
@@ -0,0 +1,13 @@
1
+ module Api
2
+ module V2
3
+ module InterfacesControllerExtensions
4
+ extend Apipie::DSL::Concern
5
+
6
+ update_api(:create, :update) do
7
+ param :interface, Hash do
8
+ param :execution, :bool, :desc => N_('Should this interface be used for remote execution?')
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -44,29 +44,33 @@ module ForemanRemoteExecution
44
44
  @execution_status_label ||= get_status(HostStatus::ExecutionStatus).to_label(options)
45
45
  end
46
46
 
47
+ # rubocop:disable Naming/MemoizedInstanceVariableName
47
48
  def host_params_hash
48
- params = super
49
- keys = remote_execution_ssh_keys
50
- source = 'global'
51
- if keys.present?
52
- value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| [v].flatten.compact }
53
- params['remote_execution_ssh_keys'] = {:value => value + keys, :safe_value => safe_value + keys, :source => source}
54
- end
55
- [:remote_execution_ssh_user, :remote_execution_effective_user_method,
56
- :remote_execution_connect_by_ip].each do |key|
57
- value = Setting[key]
58
- params[key.to_s] = {:value => value, :safe_value => value, :source => source} unless params.key?(key.to_s)
59
- end
60
- params
49
+ @cached_rex_host_params_hash ||= extend_host_params_hash(super)
61
50
  end
51
+ # rubocop:enable Naming/MemoizedInstanceVariableName
62
52
 
63
53
  def execution_interface
64
54
  get_interface_by_flag(:execution)
65
55
  end
66
56
 
57
+ def set_execution_interface(identifier)
58
+ if interfaces.find_by(identifier: identifier).nil?
59
+ msg = (N_("Interface with the '%s' identifier was specified as a remote execution interface, however the interface was not found on the host. If the interface exists, it needs to be created in Foreman during the registration.") % identifier)
60
+ raise ActiveRecord::RecordNotFound, msg
61
+ end
62
+
63
+ # Only one interface at time can be used for REX, all other must be set to false
64
+ interfaces.each { |int| int.execution = (int.identifier == identifier) }
65
+ interfaces.each(&:save!)
66
+ end
67
+
67
68
  def remote_execution_proxies(provider, authorized = true)
68
69
  proxies = {}
69
- proxies[:subnet] = execution_interface.subnet.remote_execution_proxies.with_features(provider) if execution_interface&.subnet
70
+ proxies[:subnet] = []
71
+ proxies[:subnet] += execution_interface.subnet6.remote_execution_proxies.with_features(provider) if execution_interface&.subnet6
72
+ proxies[:subnet] += execution_interface.subnet.remote_execution_proxies.with_features(provider) if execution_interface&.subnet
73
+ proxies[:subnet].uniq!
70
74
  proxies[:fallback] = smart_proxies.with_features(provider) if Setting[:remote_execution_fallback_proxy]
71
75
 
72
76
  if Setting[:remote_execution_global_proxy]
@@ -102,8 +106,28 @@ module ForemanRemoteExecution
102
106
  super(*args)
103
107
  end
104
108
 
109
+ def clear_host_parameters_cache!
110
+ super
111
+ @cached_rex_host_params_hash = nil
112
+ end
113
+
105
114
  private
106
115
 
116
+ def extend_host_params_hash(params)
117
+ keys = remote_execution_ssh_keys
118
+ source = 'global'
119
+ if keys.present?
120
+ value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| [v].flatten.compact }
121
+ params['remote_execution_ssh_keys'] = {:value => value + keys, :safe_value => safe_value + keys, :source => source}
122
+ end
123
+ [:remote_execution_ssh_user, :remote_execution_effective_user_method,
124
+ :remote_execution_connect_by_ip].each do |key|
125
+ value = Setting[key]
126
+ params[key.to_s] = {:value => value, :safe_value => value, :source => source} unless params.key?(key.to_s)
127
+ end
128
+ params
129
+ end
130
+
107
131
  def build_required_interfaces(attrs = {})
108
132
  super(attrs)
109
133
  self.primary_interface.execution = true if self.execution_interface.nil?
@@ -4,7 +4,7 @@ class ForeignInputSet < ApplicationRecord
4
4
  class CircularDependencyError < Foreman::Exception
5
5
  end
6
6
 
7
- attr_exportable :exclude, :include, :include_all, :template => ->(input_set) { input_set.template.name }
7
+ attr_exportable :exclude, :include, :include_all, :template => ->(input_set) { input_set.target_template.name }
8
8
 
9
9
  belongs_to :template
10
10
  belongs_to :target_template, :class_name => 'Template'
@@ -22,6 +22,7 @@ class JobInvocation < ApplicationRecord
22
22
  validates :job_category, :presence => true
23
23
  validates_associated :targeting, :all_template_invocations
24
24
 
25
+ scoped_search :on => :id, :complete_value => true
25
26
  scoped_search :on => :job_category, :complete_value => true
26
27
  scoped_search :on => :description, :complete_value => true
27
28
 
@@ -173,7 +174,7 @@ class JobInvocation < ApplicationRecord
173
174
 
174
175
  def total_hosts_count
175
176
  if targeting.resolved?
176
- targeting.hosts.count
177
+ task&.main_action&.total_count || targeting.hosts.count
177
178
  else
178
179
  _('N/A')
179
180
  end
@@ -243,6 +244,10 @@ class JobInvocation < ApplicationRecord
243
244
  !task.pending?
244
245
  end
245
246
 
247
+ def missing_hosts_count
248
+ targeting.resolved? ? total_hosts_count - targeting.hosts.count : 0
249
+ end
250
+
246
251
  private
247
252
 
248
253
  def failed_template_invocations
@@ -482,7 +482,7 @@ class JobInvocationComposer
482
482
 
483
483
  def input_value_for(input)
484
484
  invocations = pattern_template_invocations
485
- default = TemplateInvocationInputValue.new
485
+ default = TemplateInvocationInputValue.new(:template_input_id => input.id)
486
486
  invocations.map(&:input_values).flatten.detect { |iv| iv.template_input_id == input.id } || default
487
487
  end
488
488
 
@@ -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 ]
@@ -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,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
 
@@ -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
@@ -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 %>
@@ -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 %>
@@ -1,12 +1,16 @@
1
1
  <% items = [{ :caption => _('Job invocations'), :url => job_invocations_path },
2
2
  { :caption => @template_invocation.job_invocation.description,
3
- :url => job_invocation_path(@template_invocation.job_invocation_id) },
4
- { :caption => _('Template Invocation for %s') % @template_invocation.host.name }] %>
3
+ :url => job_invocation_path(@template_invocation.job_invocation_id) }]
5
4
 
6
- <% breadcrumbs(:resource_url => template_invocations_api_job_invocation_path(@template_invocation.job_invocation_id),
5
+ if @host
6
+ items << { :caption => _('Template Invocation for %s') % @host.name }
7
+ breadcrumbs(:resource_url => template_invocations_api_job_invocation_path(@template_invocation.job_invocation_id),
7
8
  :name_field => 'host_name',
8
9
  :switcher_item_url => template_invocation_path(':id'),
9
10
  :items => items)
11
+ else
12
+ breadcrumbs(items: items, switchable: false)
13
+ end
10
14
  %>
11
15
 
12
16
  <% stylesheet 'foreman_remote_execution/foreman_remote_execution' %>
@@ -18,31 +22,34 @@
18
22
  <%= button_group(link_to_function(_('Toggle command'), '$("div.preview").toggle()', :class => 'btn btn-default'),
19
23
  link_to_function(_('Toggle STDERR'), '$("div.line.stderr").toggle()', :class => 'btn btn-default'),
20
24
  link_to_function(_('Toggle STDOUT'), '$("div.line.stdout").toggle()', :class => 'btn btn-default'),
21
- link_to_function(_('Toggle DEBUG'), '$("div.line.debug").toggle()', :class => 'btn btn-default')) %>
25
+ link_to_function(_('Toggle DEBUG'), '$("div.line.debug").toggle()', :class => 'btn btn-default')) if @host %>
22
26
  <%= button_group(template_invocation_task_buttons(@template_invocation_task, @template_invocation.job_invocation)) %>
23
27
  </div>
24
28
  </div>
29
+ <% if @host %>
30
+ <h3><%= _('Target: ') %><%= link_to(@host.name, host_path(@host)) %></h3>
25
31
 
26
- <h3><%= _('Target: ') %><%= link_to(@host.name, host_path(@host)) %></h3>
27
-
28
- <div class="preview hidden">
29
- <%= preview_box(@template_invocation, @host) %>
30
- </div>
32
+ <div class="preview hidden">
33
+ <%= preview_box(@template_invocation, @host) %>
34
+ </div>
31
35
 
32
- <div class="terminal" data-refresh-url="<%= template_invocation_path(@template_invocation) %>">
33
- <% if @error %>
34
- <div class="line error"><%= @error %></div>
35
- <% else %>
36
- <%= link_to_function(_('Scroll to bottom'), '$("html, body").animate({ scrollTop: $(document).height() }, "slow");', :class => 'pull-right scroll-link-bottom') %>
36
+ <div class="terminal" data-refresh-url="<%= template_invocation_path(@template_invocation) %>">
37
+ <% if @error %>
38
+ <div class="line error"><%= @error %></div>
39
+ <% else %>
40
+ <%= link_to_function(_('Scroll to bottom'), '$("html, body").animate({ scrollTop: $(document).height() }, "slow");', :class => 'pull-right scroll-link-bottom') %>
37
41
 
38
- <div class="printable">
39
- <%= render :partial => 'output_line_set', :collection => normalize_line_sets(@line_sets) %>
40
- </div>
42
+ <div class="printable">
43
+ <%= render :partial => 'output_line_set', :collection => normalize_line_sets(@line_sets) %>
44
+ </div>
41
45
 
42
- <%= link_to_function(_('Scroll to top'), '$("html, body").animate({ scrollTop: 0 }, "slow");', :class => 'pull-right scroll-link-top') %>
43
- <% end %>
44
- </div>
46
+ <%= link_to_function(_('Scroll to top'), '$("html, body").animate({ scrollTop: 0 }, "slow");', :class => 'pull-right scroll-link-top') %>
47
+ <% end %>
48
+ </div>
45
49
 
46
- <script>
47
- <%= render :partial => 'refresh.js' %>
48
- </script>
50
+ <script>
51
+ <%= render :partial => 'refresh.js' %>
52
+ </script>
53
+ <% else %>
54
+ <%= _("Could not display data for job invocation.") %>
55
+ <% 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
@@ -55,6 +55,8 @@ 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'
59
+ post 'outputs'
58
60
  end
59
61
  end
60
62
 
@@ -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
@@ -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
@@ -74,7 +83,7 @@ module ForemanRemoteExecution
74
83
  permission :create_job_invocations, { :job_invocations => [:new, :create, :refresh, :rerun, :preview_hosts],
75
84
  'api/v2/job_invocations' => [:create, :rerun] }, :resource_type => 'JobInvocation'
76
85
  permission :view_job_invocations, { :job_invocations => [:index, :chart, :show, :auto_complete_search], :template_invocations => [:show],
77
- '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'
78
87
  permission :view_template_invocations, { :template_invocations => [:show],
79
88
  'api/v2/template_invocations' => [:template_invocations] }, :resource_type => 'TemplateInvocation'
80
89
  permission :create_template_invocations, {}, :resource_type => 'TemplateInvocation'
@@ -138,7 +147,16 @@ module ForemanRemoteExecution
138
147
  end
139
148
 
140
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
141
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
142
160
  end
143
161
  end
144
162
 
@@ -184,6 +202,7 @@ module ForemanRemoteExecution
184
202
  SmartProxy.prepend ForemanRemoteExecution::SmartProxyExtensions
185
203
  Subnet.include ForemanRemoteExecution::SubnetExtensions
186
204
 
205
+ ::Api::V2::InterfacesController.include Api::V2::InterfacesControllerExtensions
187
206
  # We need to explicitly force to load the Task model due to Rails loader
188
207
  # having issues with resolving it to Rake::Task otherwise
189
208
  require_dependency 'foreman_tasks/task'
@@ -192,6 +211,10 @@ module ForemanRemoteExecution
192
211
  RemoteExecutionProvider.register(:SSH, SSHExecutionProvider)
193
212
 
194
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
195
218
  end
196
219
 
197
220
  initializer 'foreman_remote_execution.register_gettext', after: :load_config_initializers do |_app|
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '4.0.0'.freeze
2
+ VERSION = '4.2.3'.freeze
3
3
  end
data/package.json CHANGED
@@ -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
  }
@@ -160,6 +160,35 @@ module Api
160
160
  end
161
161
  end
162
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 selected hosts in the job' do
175
+ post :outputs, params: { :id => @invocation.id, :search_query => "id = #{@invocation.targeting.host_ids.first}" }, as: :json
176
+ result = ActiveSupport::JSON.decode(@response.body)
177
+ host_output = result['outputs'].first
178
+ assert_equal host_output['host_id'], @invocation.targeting.host_ids.first
179
+ assert_equal host_output['refresh'], true
180
+ assert_equal host_output['output'], []
181
+ assert_response :success
182
+ end
183
+
184
+ test 'should provide outputs for hosts in the job matching a search query' do
185
+ get :outputs, params: { :id => @invocation.id, :search_query => "name = definitely_not_in_the_job" }
186
+ result = ActiveSupport::JSON.decode(@response.body)
187
+ assert_equal result['outputs'], []
188
+ assert_response :success
189
+ end
190
+ end
191
+
163
192
  describe 'raw output' do
164
193
  let(:fake_output) do
165
194
  (1..5).map do |i|
@@ -0,0 +1,82 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module Api
4
+ module V2
5
+ # Tests for the extra methods to play roles on a Host
6
+ class RegistrationControllerTest < ActionController::TestCase
7
+ describe 'host registration' do
8
+ let(:organization) { FactoryBot.create(:organization) }
9
+ let(:tax_location) { FactoryBot.create(:location) }
10
+ let(:template_kind) { template_kinds(:registration) }
11
+ let(:registration_template) do
12
+ FactoryBot.create(
13
+ :provisioning_template,
14
+ template_kind: template_kind,
15
+ template: 'template content <%= @host.name %>',
16
+ locations: [tax_location],
17
+ organizations: [organization]
18
+ )
19
+ end
20
+ let(:os) do
21
+ FactoryBot.create(
22
+ :operatingsystem,
23
+ :with_associations,
24
+ family: 'Redhat',
25
+ provisioning_templates: [
26
+ registration_template,
27
+ ]
28
+ )
29
+ end
30
+
31
+ let(:host_params) do
32
+ { host: { name: 'centos-test.example.com',
33
+ managed: false, build: false,
34
+ organization_id: organization.id,
35
+ location_id: tax_location.id,
36
+ operatingsystem_id: os.id } }
37
+ end
38
+
39
+ setup do
40
+ FactoryBot.create(
41
+ :os_default_template,
42
+ template_kind: template_kind,
43
+ provisioning_template: registration_template,
44
+ operatingsystem: os
45
+ )
46
+ end
47
+
48
+ describe 'remote_execution_interface' do
49
+ setup do
50
+ @host = Host.create(host_params[:host])
51
+ @interface0 = FactoryBot.create(:nic_managed, host: @host, identifier: 'dummy0', execution: false)
52
+ end
53
+
54
+ test 'with existing interface' do
55
+ params = host_params.merge(remote_execution_interface: @interface0.identifier)
56
+
57
+ post :host, params: params, session: set_session_user
58
+ assert_response :success
59
+ assert @interface0.reload.execution
60
+ end
61
+
62
+ test 'with not-existing interface' do
63
+ params = host_params.merge(remote_execution_interface: 'dummy999')
64
+
65
+ post :host, params: params, session: set_session_user
66
+ assert_response :not_found
67
+ end
68
+
69
+ test 'with multiple interfaces' do
70
+ interface1 = FactoryBot.create(:nic_managed, host: @host, identifier: 'dummy1', execution: false)
71
+ params = host_params.merge(remote_execution_interface: interface1.identifier)
72
+
73
+ post :host, params: params, session: set_session_user
74
+ assert_response :success
75
+ refute @interface0.reload.execution
76
+ assert interface1.reload.execution
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -24,6 +24,7 @@ module ForemanRemoteExecution
24
24
  OpenStruct.new(:id => uuid).tap do |o|
25
25
  o.stubs(:add_missing_task_groups)
26
26
  o.stubs(:task_groups).returns([])
27
+ o.stubs(:pending?).returns(true)
27
28
  end
28
29
  end
29
30
  let(:action) do
@@ -73,7 +74,7 @@ module ForemanRemoteExecution
73
74
  end
74
75
 
75
76
  it 'triggers the RunHostJob actions on the resolved hosts in run phase' do
76
- planned.expects(:output).returns(:planned_count => 0)
77
+ planned.expects(:output).at_most(5).returns(:planned_count => 0)
77
78
  planned.expects(:trigger).with { |*args| args[0] == Actions::RemoteExecution::RunHostJob }
78
79
  planned.create_sub_plans
79
80
  end
@@ -51,8 +51,12 @@ const TargetingHosts = ({ apiStatus, items }) => {
51
51
  };
52
52
 
53
53
  TargetingHosts.propTypes = {
54
- apiStatus: PropTypes.string.isRequired,
54
+ apiStatus: PropTypes.string,
55
55
  items: PropTypes.array.isRequired,
56
56
  };
57
57
 
58
+ TargetingHosts.defaultProps = {
59
+ apiStatus: null,
60
+ };
61
+
58
62
  export default TargetingHosts;
@@ -52,11 +52,15 @@ const TargetingHostsPage = ({
52
52
  TargetingHostsPage.propTypes = {
53
53
  handleSearch: PropTypes.func.isRequired,
54
54
  searchQuery: PropTypes.string.isRequired,
55
- apiStatus: PropTypes.string.isRequired,
55
+ apiStatus: PropTypes.string,
56
56
  items: PropTypes.array.isRequired,
57
57
  totalHosts: PropTypes.number.isRequired,
58
58
  pagination: PropTypes.object.isRequired,
59
59
  handlePagination: PropTypes.func.isRequired,
60
60
  };
61
61
 
62
+ TargetingHostsPage.defaultProps = {
63
+ apiStatus: null,
64
+ };
65
+
62
66
  export default TargetingHostsPage;
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_remote_execution
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Remote Execution team
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-13 00:00:00.000000000 Z
11
+ date: 2021-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deface
@@ -154,6 +154,8 @@ files:
154
154
  - app/controllers/concerns/foreman/controller/parameters/job_template.rb
155
155
  - app/controllers/concerns/foreman/controller/parameters/remote_execution_feature.rb
156
156
  - app/controllers/concerns/foreman/controller/parameters/targeting.rb
157
+ - app/controllers/foreman_remote_execution/concerns/api/v2/registration_controller_extensions.rb
158
+ - app/controllers/foreman_remote_execution/concerns/api/v2/subnets_controller_extensions.rb
157
159
  - app/controllers/job_invocations_controller.rb
158
160
  - app/controllers/job_templates_controller.rb
159
161
  - app/controllers/remote_execution_features_controller.rb
@@ -170,6 +172,7 @@ files:
170
172
  - app/lib/foreman_remote_execution/renderer/scope/input.rb
171
173
  - app/lib/proxy_api/remote_execution_ssh.rb
172
174
  - app/mailers/.gitkeep
175
+ - app/models/concerns/api/v2/interfaces_controller_extensions.rb
173
176
  - app/models/concerns/foreman_remote_execution/bookmark_extensions.rb
174
177
  - app/models/concerns/foreman_remote_execution/errors_flattener.rb
175
178
  - app/models/concerns/foreman_remote_execution/foreman_tasks_cleaner_extensions.rb
@@ -212,6 +215,7 @@ files:
212
215
  - app/views/api/v2/foreign_input_sets/index.json.rabl
213
216
  - app/views/api/v2/foreign_input_sets/main.json.rabl
214
217
  - app/views/api/v2/foreign_input_sets/show.json.rabl
218
+ - app/views/api/v2/interfaces/execution_flag.json.rabl
215
219
  - app/views/api/v2/job_invocations/base.json.rabl
216
220
  - app/views/api/v2/job_invocations/create.json.rabl
217
221
  - app/views/api/v2/job_invocations/index.json.rabl
@@ -223,11 +227,13 @@ files:
223
227
  - app/views/api/v2/job_templates/main.json.rabl
224
228
  - app/views/api/v2/job_templates/show.json.rabl
225
229
  - app/views/api/v2/job_templates/update.json.rabl
230
+ - app/views/api/v2/registration/_form.html.erb
226
231
  - app/views/api/v2/remote_execution_features/base.json.rabl
227
232
  - app/views/api/v2/remote_execution_features/index.json.rabl
228
233
  - app/views/api/v2/remote_execution_features/main.json.rabl
229
234
  - app/views/api/v2/remote_execution_features/show.json.rabl
230
235
  - app/views/api/v2/smart_proxies/pubkey.json.rabl
236
+ - app/views/api/v2/subnets/remote_execution_proxies.json.rabl
231
237
  - app/views/api/v2/template_invocations/base.json.rabl
232
238
  - app/views/api/v2/template_invocations/template_invocations.json.rabl
233
239
  - app/views/dashboard/.gitkeep
@@ -324,6 +330,7 @@ files:
324
330
  - db/migrate/20180913101042_add_randomized_ordering_to_targeting.rb
325
331
  - db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb
326
332
  - db/migrate/20200623073022_rename_sudo_password_to_effective_user_password.rb
333
+ - db/migrate/20200820122057_add_proxy_selector_override_to_remote_execution_feature.rb
327
334
  - db/seeds.d/100-assign_features_with_templates.rb
328
335
  - db/seeds.d/20-permissions.rb
329
336
  - db/seeds.d/50-notification_blueprints.rb
@@ -372,6 +379,7 @@ files:
372
379
  - test/functional/api/v2/foreign_input_sets_controller_test.rb
373
380
  - test/functional/api/v2/job_invocations_controller_test.rb
374
381
  - test/functional/api/v2/job_templates_controller_test.rb
382
+ - test/functional/api/v2/registration_controller_test.rb
375
383
  - test/functional/api/v2/remote_execution_features_controller_test.rb
376
384
  - test/functional/api/v2/template_invocations_controller_test.rb
377
385
  - test/functional/cockpit_controller_test.rb
@@ -439,7 +447,7 @@ homepage: https://github.com/theforeman/foreman_remote_execution
439
447
  licenses:
440
448
  - GPL-3.0
441
449
  metadata: {}
442
- post_install_message:
450
+ post_install_message:
443
451
  rdoc_options: []
444
452
  require_paths:
445
453
  - lib
@@ -454,8 +462,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
454
462
  - !ruby/object:Gem::Version
455
463
  version: '0'
456
464
  requirements: []
457
- rubygems_version: 3.0.3
458
- signing_key:
465
+ rubygems_version: 3.1.2
466
+ signing_key:
459
467
  specification_version: 4
460
468
  summary: A plugin bringing remote execution to the Foreman, completing the config
461
469
  management functionality with remote management functionality.
@@ -466,6 +474,7 @@ test_files:
466
474
  - test/functional/api/v2/foreign_input_sets_controller_test.rb
467
475
  - test/functional/api/v2/job_invocations_controller_test.rb
468
476
  - test/functional/api/v2/job_templates_controller_test.rb
477
+ - test/functional/api/v2/registration_controller_test.rb
469
478
  - test/functional/api/v2/remote_execution_features_controller_test.rb
470
479
  - test/functional/api/v2/template_invocations_controller_test.rb
471
480
  - test/functional/cockpit_controller_test.rb