foreman_remote_execution 4.5.0 → 4.5.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 113fb04d80f92308f55da87e148b2c30de510b5a97dda5c400bdac598d3a407f
4
- data.tar.gz: 42a44014bbb85af6ccef059d4d716a9e840865be30735cea61fb65cd94a63d2f
3
+ metadata.gz: e6f79124fa44d1ce9756c7c680a43ea7c9c1d309836641e4f1d91fe713a75fdb
4
+ data.tar.gz: 1db640940ff3ba7a44b183c017ee0c1c3262e5263553365e930695b5f1686b8e
5
5
  SHA512:
6
- metadata.gz: 6321f8795de48743746748a71b3a64d4c75de23f55bfe0e79bfd660a54ecd2b5522f10554fa96c16d11350a6800457b47ae0a82fec7f905142f9d062103b3ce6
7
- data.tar.gz: 41962649438f42c27845b6e7b6694cff343c4f44e8b7771534a25127458ed2b9a15b404e319e00958f36f81a0d139733820873dd5a2771cb5efeaaebbbfa7223
6
+ metadata.gz: 9c105b218161cc5e509c5da55ca76d339d37d217e318aff139609361ece08ff0fa36c0b70fbb41e0c2bfac2633c86218fd4539e762e1c4f0e1ed49dc778ec728
7
+ data.tar.gz: 9d148bc9b8af5f27c6b5bb91d8e28ea27bbff870ce1239a2e87413d5cf49b5740ccad18f713f22a1d348ff5fe185bdef015dd2bbd1b85f2efe802547e88fe193
@@ -67,7 +67,7 @@ class JobInvocationsController < ApplicationController
67
67
  end
68
68
 
69
69
  def index
70
- @job_invocations = resource_base_search_and_page.with_task.order('job_invocations.id DESC')
70
+ @job_invocations = resource_base_search_and_page.preload(:task, :targeting).order('job_invocations.id DESC')
71
71
  end
72
72
 
73
73
  # refreshes the form
@@ -47,12 +47,16 @@ module Actions
47
47
  script = renderer.render
48
48
  raise _('Failed rendering template: %s') % renderer.error_message unless script
49
49
 
50
+ first_execution = host.executed_through_proxies.where(:id => proxy.id).none?
51
+ host.executed_through_proxies << proxy if first_execution
52
+
50
53
  additional_options = { :hostname => provider.find_ip_or_hostname(host),
51
54
  :script => script,
52
55
  :execution_timeout_interval => job_invocation.execution_timeout_interval,
53
56
  :secrets => secrets(host, job_invocation, provider),
54
57
  :use_batch_triggering => true,
55
- :use_concurrency_control => options[:use_concurrency_control]}
58
+ :use_concurrency_control => options[:use_concurrency_control],
59
+ :first_execution => first_execution }
56
60
  action_options = provider.proxy_command_options(template_invocation, host)
57
61
  .merge(additional_options)
58
62
 
@@ -6,6 +6,8 @@ module ForemanRemoteExecution
6
6
  has_many :template_invocations, :dependent => :destroy, :foreign_key => 'host_id'
7
7
  has_one :execution_status_object, :class_name => 'HostStatus::ExecutionStatus', :foreign_key => 'host_id', :dependent => :destroy
8
8
  has_many :run_host_job_tasks, :through => :template_invocations
9
+ has_many :host_proxy_invocations, :foreign_key => 'host_id', :dependent => :destroy
10
+ has_many :executed_through_proxies, :through => :host_proxy_invocations, :source => 'smart_proxy'
9
11
 
10
12
  scoped_search :relation => :run_host_job_tasks, :on => :result, :rename => 'job_invocation.result',
11
13
  :ext_method => :search_by_job_invocation,
@@ -1,5 +1,11 @@
1
1
  module ForemanRemoteExecution
2
2
  module SmartProxyExtensions
3
+ def self.prepended(base)
4
+ base.instance_eval do
5
+ has_many :host_proxy_invocations, :dependent => :destroy
6
+ end
7
+ end
8
+
3
9
  def pubkey
4
10
  self[:pubkey] || update_pubkey
5
11
  end
@@ -0,0 +1,4 @@
1
+ class HostProxyInvocation < ApplicationRecord
2
+ belongs_to :host, :class_name => 'Host::Managed', :inverse_of => :host_proxy_invocations
3
+ belongs_to :smart_proxy
4
+ end
@@ -64,11 +64,11 @@ class HostStatus::ExecutionStatus < HostStatus::Status
64
64
 
65
65
  case status
66
66
  when OK
67
- [ "foreman_tasks_tasks.state = 'stopped' AND result = 'success'" ]
67
+ [ "foreman_tasks_tasks.state = 'stopped' AND foreman_tasks_tasks.result = 'success'" ]
68
68
  when CANCELLED
69
- [ "foreman_tasks_tasks.state = 'stopped' AND result = 'cancelled'" ]
69
+ [ "foreman_tasks_tasks.state = 'stopped' AND foreman_tasks_tasks.result = 'cancelled'" ]
70
70
  when ERROR
71
- [ "foreman_tasks_tasks.state = 'stopped' AND (result = 'error' OR result = 'warning')" ]
71
+ [ "foreman_tasks_tasks.state = 'stopped' AND (foreman_tasks_tasks.result = 'error' OR foreman_tasks_tasks.result = 'warning')" ]
72
72
  when QUEUED
73
73
  [ "foreman_tasks_tasks.state = 'scheduled' OR foreman_tasks_tasks.state IS NULL" ]
74
74
  when RUNNING
@@ -65,8 +65,6 @@ class JobInvocation < ApplicationRecord
65
65
  scoped_search :on => 'pattern_template_name', :rename => 'pattern_template_name', :operators => ['= '],
66
66
  :complete_value => false, :only_explicit => true, :ext_method => :search_by_pattern_template
67
67
 
68
- scope :with_task, -> { references(:task) }
69
-
70
68
  scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id'
71
69
 
72
70
  scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring',
@@ -99,7 +97,7 @@ class JobInvocation < ApplicationRecord
99
97
  def self.search_by_status(key, operator, value)
100
98
  conditions = HostStatus::ExecutionStatus::ExecutionTaskStatusMapper.sql_conditions_for(value)
101
99
  conditions[0] = "NOT (#{conditions[0]})" if operator == '<>'
102
- { :conditions => sanitize_sql_for_conditions(conditions), :include => :task }
100
+ { :conditions => sanitize_sql_for_conditions(conditions), :joins => :task }
103
101
  end
104
102
 
105
103
  def self.search_by_recurring_logic(key, operator, value)
@@ -193,11 +191,16 @@ class JobInvocation < ApplicationRecord
193
191
  end
194
192
 
195
193
  def total_hosts_count
194
+ count = _('N/A')
195
+
196
196
  if targeting.resolved?
197
- task&.main_action&.total_count || targeting.hosts.count
198
- else
199
- _('N/A')
197
+ count = if task&.main_action.respond_to?(:total_count)
198
+ task.main_action.total_count
199
+ else
200
+ targeting.hosts.count
201
+ end
200
202
  end
203
+ count
201
204
  end
202
205
 
203
206
  def pattern_template_invocation_for_host(host)
@@ -174,7 +174,7 @@ class JobInvocationComposer
174
174
  input = template.template_inputs_with_foreign.find { |i| i.name == name }
175
175
  unless input
176
176
  raise ::Foreman::Exception, _('Unknown input %{input_name} for template %{template_name}') %
177
- { :input_name => name, :template_name => template.name }
177
+ { :input_name => name, :template_name => template.name }
178
178
  end
179
179
  { :template_input_id => input.id, :value => value }
180
180
  end
@@ -405,7 +405,7 @@ class JobInvocationComposer
405
405
  job_invocation.effective_user_password = params[:effective_user_password]
406
406
 
407
407
  if @reruns && job_invocation.targeting.static?
408
- job_invocation.targeting.host_ids = JobInvocation.find(@reruns).targeting.host_ids
408
+ job_invocation.targeting.assign_host_ids(JobInvocation.find(@reruns).targeting.host_ids)
409
409
  job_invocation.targeting.mark_resolved!
410
410
  end
411
411
 
@@ -50,7 +50,7 @@ class RemoteExecutionProvider
50
50
  method = host_setting(host, :remote_execution_effective_user_method)
51
51
  unless EFFECTIVE_USER_METHODS.include?(method)
52
52
  raise _('Effective user method "%{current_value}" is not one of %{valid_methods}') %
53
- { :current_value => method, :valid_methods => EFFECTIVE_USER_METHODS}
53
+ { :current_value => method, :valid_methods => EFFECTIVE_USER_METHODS}
54
54
  end
55
55
  method
56
56
  end
@@ -70,13 +70,13 @@ class Setting::RemoteExecution < Setting
70
70
  self.set('remote_execution_form_job_template',
71
71
  N_('Choose a job template that is pre-selected in job invocation form'),
72
72
  'Run Command - SSH Default',
73
- _('Form Job Template'),
73
+ N_('Form Job Template'),
74
74
  nil,
75
75
  { :collection => proc { Hash[JobTemplate.unscoped.map { |template| [template.name, template.name] }] } }),
76
76
  self.set('remote_execution_job_invocation_report_template',
77
77
  N_('Select a report template used for generating a report for a particular remote execution job'),
78
78
  'Jobs - Invocation report template',
79
- _('Job Invocation Report Template'),
79
+ N_('Job Invocation Report Template'),
80
80
  nil,
81
81
  { :collection => proc { self.job_invocation_report_templates_select } }),
82
82
  ]
@@ -46,9 +46,13 @@ class Targeting < ApplicationRecord
46
46
  # pluck(:id) returns duplicate results for HostCollections
47
47
  host_ids = User.as(user.login) { Host.authorized(RESOLVE_PERMISSION, Host).search_for(search_query).order(:name, :id).pluck(:id).uniq }
48
48
  host_ids.shuffle!(random: Random.new) if randomized_ordering
49
+ self.assign_host_ids(host_ids)
50
+ self.save(:validate => false)
51
+ end
52
+
53
+ def assign_host_ids(host_ids)
49
54
  # this can be optimized even more, by introducing bulk insert
50
55
  self.targeting_hosts.build(host_ids.map { |id| { :host_id => id } })
51
- self.save(:validate => false)
52
56
  end
53
57
 
54
58
  def dynamic?
@@ -21,7 +21,7 @@
21
21
  <% @job_invocations.each do |invocation| %>
22
22
  <tr>
23
23
  <td class="text_warp"><%= link_to_if_authorized invocation_description(invocation), hash_for_job_invocation_path(invocation).merge(:auth_object => invocation, :permission => :view_job_invocations, :authorizer => authorizer) %></td>
24
- <td><%= trunc_with_tooltip(invocation&.targeting&.search_query, 15) %></td>
24
+ <td><%= trunc_with_tooltip(invocation.targeting.search_query, 15) %></td>
25
25
  <td><%= link_to_invocation_task_if_authorized(invocation) %></td>
26
26
  <td><%= invocation_result(invocation, :success_count) %></td>
27
27
  <td><%= invocation_result(invocation, :failed_count) %></td>
@@ -0,0 +1,12 @@
1
+ class AddHostProxyInvocations < ActiveRecord::Migration[6.0]
2
+ def change
3
+ # rubocop:disable Rails/CreateTableWithTimestamps
4
+ create_table :host_proxy_invocations do |t|
5
+ t.references :host, :null => false
6
+ t.references :smart_proxy, :null => false
7
+ end
8
+ # rubocop:enable Rails/CreateTableWithTimestamps
9
+
10
+ add_index :host_proxy_invocations, [:host_id, :smart_proxy_id], unique: true
11
+ end
12
+ end
@@ -201,12 +201,10 @@ module ForemanRemoteExecution
201
201
 
202
202
  Host::Managed.prepend ForemanRemoteExecution::HostExtensions
203
203
  Host::Managed.include ForemanTasks::Concerns::HostActionSubject
204
- Host::Managed.include ForemanRemoteExecution::Orchestration::SSH
205
204
 
206
205
  (Nic::Base.descendants + [Nic::Base]).each do |klass|
207
206
  klass.send(:include, ForemanRemoteExecution::NicExtensions)
208
207
  end
209
- Nic::Managed.include ForemanRemoteExecution::Orchestration::SSH
210
208
 
211
209
  Bookmark.include ForemanRemoteExecution::BookmarkExtensions
212
210
  HostsHelper.prepend ForemanRemoteExecution::HostsHelperExtensions
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '4.5.0'.freeze
2
+ VERSION = '4.5.1'.freeze
3
3
  end
@@ -864,7 +864,9 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
864
864
  it 'marks targeting as resolved if static' do
865
865
  created = JobInvocationComposer.from_job_invocation(job_invocation).job_invocation
866
866
  assert created.targeting.resolved?
867
- assert_equal job_invocation.template_invocations_host_ids, created.targeting.host_ids
867
+ created.targeting.save
868
+ created.targeting.reload
869
+ assert_equal job_invocation.template_invocations_host_ids, created.targeting.targeting_hosts.pluck(:host_id)
868
870
  end
869
871
 
870
872
  it 'takes randomized_ordering from the original job invocation when rerunning failed' do
@@ -874,6 +876,17 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
874
876
  composer = JobInvocationComposer.from_job_invocation(job_invocation, :host_ids => host_ids)
875
877
  assert composer.job_invocation.targeting.randomized_ordering
876
878
  end
879
+
880
+ it 'works with invalid hosts' do
881
+ host = job_invocation.targeting.hosts.first
882
+ ::Host::Managed.any_instance.stubs(:valid?).returns(false)
883
+ composer = JobInvocationComposer.from_job_invocation(job_invocation, {})
884
+ targeting = composer.compose.job_invocation.targeting
885
+ targeting.save!
886
+ targeting.reload
887
+ assert targeting.valid?
888
+ assert_equal targeting.hosts.pluck(:id), [host.id]
889
+ end
877
890
  end
878
891
 
879
892
  describe '.for_feature' do
@@ -10,7 +10,7 @@ class JobInvocationTest < ActiveSupport::TestCase
10
10
  end
11
11
 
12
12
  it 'is able to perform search through job invocations' do
13
- found_jobs = JobInvocation.search_for(%{job_category = "#{job_invocation.job_category}"}).paginate(:page => 1).with_task.order('job_invocations.id DESC')
13
+ found_jobs = JobInvocation.search_for(%{job_category = "#{job_invocation.job_category}"}).paginate(:page => 1).order('job_invocations.id DESC')
14
14
  _(found_jobs).must_equal [job_invocation]
15
15
  end
16
16
 
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.5.0
4
+ version: 4.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Remote Execution team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-31 00:00:00.000000000 Z
11
+ date: 2021-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deface
@@ -171,7 +171,6 @@ files:
171
171
  - app/models/concerns/foreman_remote_execution/foreman_tasks_triggering_extensions.rb
172
172
  - app/models/concerns/foreman_remote_execution/host_extensions.rb
173
173
  - app/models/concerns/foreman_remote_execution/nic_extensions.rb
174
- - app/models/concerns/foreman_remote_execution/orchestration/ssh.rb
175
174
  - app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb
176
175
  - app/models/concerns/foreman_remote_execution/subnet_extensions.rb
177
176
  - app/models/concerns/foreman_remote_execution/taxonomy_extensions.rb
@@ -180,6 +179,7 @@ files:
180
179
  - app/models/concerns/foreman_remote_execution/template_overrides.rb
181
180
  - app/models/concerns/foreman_remote_execution/user_extensions.rb
182
181
  - app/models/foreign_input_set.rb
182
+ - app/models/host_proxy_invocation.rb
183
183
  - app/models/host_status/execution_status.rb
184
184
  - app/models/input_template_renderer.rb
185
185
  - app/models/invocation_provider_input_value.rb
@@ -324,6 +324,7 @@ files:
324
324
  - db/migrate/20200623073022_rename_sudo_password_to_effective_user_password.rb
325
325
  - db/migrate/20200820122057_add_proxy_selector_override_to_remote_execution_feature.rb
326
326
  - db/migrate/20210312074713_add_provider_inputs.rb
327
+ - db/migrate/2021051713291621250977_add_host_proxy_invocations.rb
327
328
  - db/seeds.d/100-assign_features_with_templates.rb
328
329
  - db/seeds.d/20-permissions.rb
329
330
  - db/seeds.d/50-notification_blueprints.rb
@@ -380,7 +381,6 @@ files:
380
381
  - test/functional/job_templates_controller_test.rb
381
382
  - test/functional/ui_job_wizard_controller_test.rb
382
383
  - test/helpers/remote_execution_helper_test.rb
383
- - test/models/orchestration/ssh_test.rb
384
384
  - test/support/remote_execution_helper.rb
385
385
  - test/test_plugin_helper.rb
386
386
  - test/unit/actions/run_host_job_test.rb
@@ -518,7 +518,6 @@ test_files:
518
518
  - test/functional/job_templates_controller_test.rb
519
519
  - test/functional/ui_job_wizard_controller_test.rb
520
520
  - test/helpers/remote_execution_helper_test.rb
521
- - test/models/orchestration/ssh_test.rb
522
521
  - test/support/remote_execution_helper.rb
523
522
  - test/test_plugin_helper.rb
524
523
  - test/unit/actions/run_host_job_test.rb
@@ -1,70 +0,0 @@
1
- module ForemanRemoteExecution
2
- module Orchestration::SSH
3
- extend ActiveSupport::Concern
4
-
5
- included do
6
- before_destroy :ssh_destroy
7
- after_validation :queue_ssh_destroy
8
- register_rebuild(:queue_ssh_destroy, N_("SSH_#{self.to_s.split('::').first}"))
9
- end
10
-
11
- def drop_from_known_hosts(proxy_id)
12
- _, _, target = host_kind_target
13
- return true if target.nil?
14
-
15
- proxy = ::SmartProxy.find(proxy_id)
16
- begin
17
- proxy.drop_host_from_known_hosts(target)
18
- rescue ::ProxyAPI::ProxyException => e
19
- if e.wrapped_exception.is_a?(RestClient::NotFound)
20
- # ignore 404 when known_hosts entry is missing or the module was not enabled
21
- Foreman::Logging.exception "Proxy failed to delete SSH known_hosts for #{name}, #{ip}", e, :level => :error
22
- else
23
- raise e
24
- end
25
- rescue => e
26
- Rails.logger.warn e.message
27
- return false
28
- end
29
- true
30
- end
31
-
32
- def ssh_destroy
33
- logger.debug "Scheduling SSH known_hosts cleanup"
34
-
35
- host, _kind, _target = host_kind_target
36
- # #remote_execution_proxies may not be defined on the host object in some case
37
- # for example Host::Discovered does not have it defined, even though these hosts
38
- # have Nic::Managed interfaces associated with them
39
- proxies = (host.try(:remote_execution_proxies, 'SSH') || {}).values
40
- proxies.flatten.uniq.each do |proxy|
41
- queue.create(id: queue_id(proxy.id), name: _("Remove SSH known hosts for %s") % self,
42
- priority: 200, action: [self, :drop_from_known_hosts, proxy.id])
43
- end
44
- end
45
-
46
- def queue_ssh_destroy
47
- should_drop_from_known_hosts? && ssh_destroy
48
- end
49
-
50
- def should_drop_from_known_hosts?
51
- host, = host_kind_target
52
- host && !host.new_record? && host.build && host.changes.key?('build')
53
- end
54
-
55
- private
56
-
57
- def host_kind_target
58
- if self.is_a?(::Host::Base)
59
- [self, 'host', name]
60
- else
61
- [self.host, 'interface', ip]
62
- end
63
- end
64
-
65
- def queue_id(proxy_id)
66
- _, kind, id = host_kind_target
67
- "ssh_remove_known_hosts_#{kind}_#{id}_#{proxy_id}"
68
- end
69
- end
70
- end
@@ -1,56 +0,0 @@
1
- require 'test_plugin_helper'
2
-
3
- class SSHOrchestrationTest < ActiveSupport::TestCase
4
- let(:host) { FactoryBot.create(:host, :managed, :with_subnet) }
5
- let(:proxy) { FactoryBot.create(:smart_proxy, :ssh) }
6
- let(:interface) { host.interfaces.first }
7
-
8
- before { interface.subnet.remote_execution_proxies = [proxy] }
9
-
10
- it 'attempts to drop IP address and hostname from smart proxies on destroy' do
11
- host.stubs(:skip_orchestration?).returns false
12
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(interface.ip)
13
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(host.name)
14
- host.destroy
15
- end
16
-
17
- it 'attempts to drop IP address and hostname from smart proxies on rebuild' do
18
- host.stubs(:skip_orchestration?).returns false
19
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(interface.ip)
20
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(host.name)
21
-
22
- host.build = true
23
- host.save!
24
-
25
- ids = ["ssh_remove_known_hosts_interface_#{interface.ip}_#{proxy.id}",
26
- "ssh_remove_known_hosts_host_#{host.name}_#{proxy.id}"]
27
- _(host.queue.task_ids).must_equal ids
28
- _(host.queue.items.map(&:status)).must_equal %w(completed completed)
29
- end
30
-
31
- it 'does not fail on 404 from the smart proxy' do
32
- host.stubs(:skip_orchestration?).returns false
33
- ::ProxyAPI::RemoteExecutionSSH.any_instance.expects(:delete).raises(RestClient::ResourceNotFound).twice
34
- host.build = true
35
- host.save!
36
- ids = ["ssh_remove_known_hosts_interface_#{interface.ip}_#{proxy.id}",
37
- "ssh_remove_known_hosts_host_#{host.name}_#{proxy.id}"]
38
- _(host.queue.task_ids).must_equal ids
39
- _(host.queue.items.map(&:status)).must_equal %w(completed completed)
40
- end
41
-
42
- it 'does not trigger the removal when creating a new host' do
43
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).never
44
- host = Host::Managed.new(:name => 'test', :ip => '127.0.0.1')
45
- host.stubs(:skip_orchestration?).returns false
46
- _(host.queue.task_ids).must_equal []
47
- end
48
-
49
- it 'does not call to the proxy when target is nil' do
50
- host.stubs(:skip_orchestration?).returns false
51
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(host.name)
52
- host.interfaces.first.stubs(:ip)
53
- host.destroy
54
- _(host.queue.items.map(&:status)).must_equal %w(completed completed)
55
- end
56
- end