foreman_remote_execution 4.0.0 → 4.2.3

Sign up to get free protection for your applications and to get access to all the features.
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