foreman_remote_execution 4.5.0 → 4.5.1

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