foreman_remote_execution 1.5.1 → 1.5.2

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 (30) hide show
  1. checksums.yaml +5 -5
  2. data/app/controllers/api/v2/job_invocations_controller.rb +1 -1
  3. data/app/helpers/remote_execution_helper.rb +2 -0
  4. data/app/lib/actions/remote_execution/run_host_job.rb +14 -1
  5. data/app/lib/actions/remote_execution/run_hosts_job.rb +6 -2
  6. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -3
  7. data/app/models/host_status/execution_status.rb +5 -1
  8. data/app/models/job_invocation.rb +4 -3
  9. data/app/models/job_invocation_composer.rb +2 -0
  10. data/app/models/job_template.rb +21 -25
  11. data/app/models/remote_execution_provider.rb +10 -1
  12. data/app/models/setting/remote_execution.rb +13 -4
  13. data/app/models/ssh_execution_provider.rb +3 -1
  14. data/app/views/job_invocations/_form.html.erb +1 -0
  15. data/app/views/job_invocations/show.html.erb +3 -1
  16. data/app/views/job_templates/edit.html.erb +13 -0
  17. data/app/views/job_templates/index.html.erb +1 -1
  18. data/db/migrate/20180411160809_add_sudo_password_to_job_invocation.rb +5 -0
  19. data/foreman_remote_execution.gemspec +2 -2
  20. data/lib/foreman_remote_execution/engine.rb +3 -0
  21. data/lib/foreman_remote_execution/version.rb +1 -1
  22. data/test/functional/api/v2/job_invocations_controller_test.rb +1 -1
  23. data/test/unit/actions/run_host_job_test.rb +63 -0
  24. data/test/unit/actions/run_hosts_job_test.rb +1 -0
  25. data/test/unit/concerns/host_extensions_test.rb +4 -4
  26. data/test/unit/job_invocation_composer_test.rb +12 -0
  27. data/test/unit/remote_execution_provider_test.rb +28 -3
  28. metadata +10 -10
  29. data/app/models/job_template_importer.rb +0 -36
  30. data/test/unit/job_template_importer_test.rb +0 -66
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9fcd52aa2585b165caee15553d3ceaaf4fc5cf69
4
- data.tar.gz: d22298a49fa2ddc29e0d5c5ea022716461ecf527
2
+ SHA256:
3
+ metadata.gz: cee9f09b684c8a829284e5eef226b75d241e91a3622fbad2f1626ff75855c812
4
+ data.tar.gz: 160de6767048860228ab7007394d548d937d757d8f19f3cd401e1257b96950ad
5
5
  SHA512:
6
- metadata.gz: bf01f416ab54076ae74f0e13aead95fa68604e6600bac1df9936991e65e3e24fbcfaf8f97ade5c68bcb3a6b25701e27368071bed6de27a31a13b59e875e13756
7
- data.tar.gz: 3283109dbb5554f722c00bcf8632e9513521641354afcc1f86484e0eefc74da05797fd70bd522b0e95d68ead7a1b28767d9e86e18f4e1da292aa1f9ecf4f8ba3
6
+ metadata.gz: c4c7c5c3a2d9173defa65b8a9fa81de541698345a623cf870f76f70a6c83792f8ecf9a70939f103fb78b0ef5fc4e8dd763bfd541df46f965bfbe532aa1cee9e8
7
+ data.tar.gz: 9311306fda1292ff48856e6f3c05808c61f89154f07491071f9e71bafbf089b13f18ef128749ad0c8726005d4f8d0658bc551ecacd3960d069fdce16387b485d
@@ -77,7 +77,7 @@ module Api
77
77
  param :host_id, :identifier, :required => true
78
78
  param :since, String, :required => false
79
79
  def output
80
- if @nested_obj.task.delayed?
80
+ if @nested_obj.task.scheduled?
81
81
  render :json => { :refresh => true, :output => [], :delayed => true, :start_at => @nested_obj.task.start_at }
82
82
  return
83
83
  end
@@ -140,6 +140,8 @@ module RemoteExecutionHelper
140
140
  options = { :unknown_string => 'N/A' }.merge(options)
141
141
  if invocation.queued?
142
142
  options[:unknown_string]
143
+ elsif options[:output_key] == :total_count
144
+ invocation.total_hosts_count
143
145
  else
144
146
  (invocation.task.try(:output) || {}).fetch(options[:output_key], options[:unknown_string])
145
147
  end
@@ -7,6 +7,10 @@ module Actions
7
7
  middleware.do_not_use Dynflow::Middleware::Common::Transaction
8
8
  middleware.use Actions::Middleware::HideSecrets
9
9
 
10
+ def queue
11
+ ForemanRemoteExecution::DYNFLOW_QUEUE
12
+ end
13
+
10
14
  def resource_locks
11
15
  :link
12
16
  end
@@ -35,7 +39,8 @@ module Actions
35
39
  provider = template_invocation.template.provider
36
40
 
37
41
  secrets = { :ssh_password => job_invocation.password || provider.ssh_password(host),
38
- :key_passphrase => job_invocation.key_passphrase || provider.ssh_key_passphrase(host) }
42
+ :key_passphrase => job_invocation.key_passphrase || provider.ssh_key_passphrase(host),
43
+ :sudo_password => job_invocation.sudo_password || provider.sudo_password(host) }
39
44
 
40
45
  additional_options = { :hostname => provider.find_ip_or_hostname(host),
41
46
  :script => script,
@@ -49,6 +54,7 @@ module Actions
49
54
  end
50
55
 
51
56
  def finalize(*args)
57
+ update_host_status
52
58
  check_exit_status
53
59
  end
54
60
 
@@ -112,6 +118,13 @@ module Actions
112
118
 
113
119
  private
114
120
 
121
+ def update_host_status
122
+ host = Host.find(input[:host][:id])
123
+ status = (host.execution_status_object ||= HostStatus::ExecutionStatus.new)
124
+ status.status = exit_status.zero? ? HostStatus::ExecutionStatus::OK : HostStatus::ExecutionStatus::ERROR
125
+ status.save!
126
+ end
127
+
115
128
  def delegated_output
116
129
  if input[:delegated_action_id]
117
130
  super
@@ -4,9 +4,13 @@ module Actions
4
4
 
5
5
  include Dynflow::Action::WithBulkSubPlans
6
6
  include Dynflow::Action::WithPollingSubPlans
7
+ include Actions::RecurringAction
7
8
 
8
9
  middleware.use Actions::Middleware::BindJobInvocation
9
- middleware.use Actions::Middleware::RecurringLogic
10
+
11
+ def queue
12
+ ForemanRemoteExecution::DYNFLOW_QUEUE
13
+ end
10
14
 
11
15
  def delay(delay_options, job_invocation)
12
16
  task.add_missing_task_groups(job_invocation.task_group)
@@ -38,7 +42,7 @@ module Actions
38
42
  end
39
43
 
40
44
  def finalize
41
- job_invocation.password = job_invocation.key_passphrase = nil
45
+ job_invocation.password = job_invocation.key_passphrase = job_invocation.sudo_password = nil
42
46
  job_invocation.save!
43
47
 
44
48
  # creating the success notification should be the very last thing this tasks do
@@ -45,13 +45,17 @@ module ForemanRemoteExecution
45
45
  @execution_status_label ||= get_status(HostStatus::ExecutionStatus).to_label(options)
46
46
  end
47
47
 
48
- def host_params
48
+ def host_params_hash
49
49
  params = super
50
50
  keys = remote_execution_ssh_keys
51
- params['remote_execution_ssh_keys'] = keys if keys.present?
51
+ source = 'global'
52
+ if keys.present?
53
+ params['remote_execution_ssh_keys'] = {:value => keys, :safe_value => keys, :source => source}
54
+ end
52
55
  [:remote_execution_ssh_user, :remote_execution_effective_user_method,
53
56
  :remote_execution_connect_by_ip].each do |key|
54
- params[key.to_s] = Setting[key] unless params.key?(key.to_s)
57
+ value = Setting[key]
58
+ params[key.to_s] = {:value => value, :safe_value => value, :source => source} unless params.key?(key.to_s)
55
59
  end
56
60
  params
57
61
  end
@@ -15,7 +15,11 @@ class HostStatus::ExecutionStatus < HostStatus::Status
15
15
  end
16
16
 
17
17
  def to_status(options = {})
18
- ExecutionTaskStatusMapper.new(last_stopped_task).status
18
+ if self.new_record?
19
+ ExecutionTaskStatusMapper.new(last_stopped_task).status
20
+ else
21
+ self.status
22
+ end
19
23
  end
20
24
 
21
25
  def to_global(options = {})
@@ -1,9 +1,9 @@
1
1
  class JobInvocation < ApplicationRecord
2
+ audited :except => [:task_id, :targeting_id, :task_group_id, :triggering_id]
3
+
2
4
  include Authorizable
3
5
  include Encryptable
4
6
 
5
- audited :except => [ :task_id, :targeting_id, :task_group_id, :triggering_id ]
6
-
7
7
  include ForemanRemoteExecution::ErrorsFlattener
8
8
  FLATTENED_ERRORS_MAPPING = {
9
9
  :pattern_template_invocations => lambda do |template_invocation|
@@ -68,7 +68,7 @@ class JobInvocation < ApplicationRecord
68
68
 
69
69
  delegate :start_at, :to => :task, :allow_nil => true
70
70
 
71
- encrypts :password, :key_passphrase
71
+ encrypts :password, :key_passphrase, :sudo_password
72
72
 
73
73
  def self.search_by_status(key, operator, value)
74
74
  conditions = HostStatus::ExecutionStatus::ExecutionTaskStatusMapper.sql_conditions_for(value)
@@ -137,6 +137,7 @@ class JobInvocation < ApplicationRecord
137
137
  invocation.pattern_template_invocations = self.pattern_template_invocations.map(&:deep_clone)
138
138
  invocation.password = self.password
139
139
  invocation.key_passphrase = self.key_passphrase
140
+ invocation.sudo_password = self.sudo_password
140
141
  end
141
142
  end
142
143
 
@@ -15,6 +15,7 @@ class JobInvocationComposer
15
15
  :description_format => job_invocation_base[:description_format],
16
16
  :password => blank_to_nil(job_invocation_base[:password]),
17
17
  :key_passphrase => blank_to_nil(job_invocation_base[:key_passphrase]),
18
+ :sudo_password => blank_to_nil(job_invocation_base[:sudo_password]),
18
19
  :concurrency_control => concurrency_control_params,
19
20
  :execution_timeout_interval => execution_timeout_interval,
20
21
  :template_invocations => template_invocations_params }.with_indifferent_access
@@ -335,6 +336,7 @@ class JobInvocationComposer
335
336
  job_invocation.execution_timeout_interval = params[:execution_timeout_interval]
336
337
  job_invocation.password = params[:password]
337
338
  job_invocation.key_passphrase = params[:key_passphrase]
339
+ job_invocation.sudo_password = params[:sudo_password]
338
340
 
339
341
  self
340
342
  end
@@ -1,4 +1,5 @@
1
1
  class JobTemplate < ::Template
2
+ audited
2
3
  include ::Exportable
3
4
 
4
5
  class NonUniqueInputsError < Foreman::Exception
@@ -12,7 +13,6 @@ class JobTemplate < ::Template
12
13
  friendly_id :name
13
14
  include Parameterizable::ByIdName
14
15
 
15
- audited :allow_mass_assignment => true
16
16
  has_many :audits, :as => :auditable, :class_name => Audited.audit_class.name, :dependent => :nullify
17
17
  has_many :all_template_invocations, :dependent => :destroy, :foreign_key => 'template_id', :class_name => 'TemplateInvocation'
18
18
  has_many :template_invocations, -> { where('host_id IS NOT NULL') }, :foreign_key => 'template_id'
@@ -66,7 +66,7 @@ class JobTemplate < ::Template
66
66
  # Import a template from ERB, with YAML metadata in the first comment. It
67
67
  # will overwrite (sync) an existing template if options[:update] is true.
68
68
  def import_raw(contents, options = {})
69
- metadata = parse_metadata(contents)
69
+ metadata = Template.parse_metadata(contents)
70
70
  import_parsed(metadata['name'], contents, metadata, options)
71
71
  end
72
72
 
@@ -76,34 +76,17 @@ class JobTemplate < ::Template
76
76
  template
77
77
  end
78
78
 
79
- # This method is used by foreman_templates to import templates, the API should be kept compatible with it
80
- def import!(name, text, metadata, force = false)
81
- metadata = metadata.dup
82
- metadata.delete('associate')
83
- JobTemplateImporter.import!(name, text, metadata)
84
- end
85
-
86
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
87
- def import_parsed(name, text, metadata, options = {})
79
+ def import_parsed(name, text, _metadata, options = {})
88
80
  transaction do
89
- return if metadata.blank? || metadata.delete('kind') != 'job_template' ||
90
- (metadata.key?('model') && metadata.delete('model') != self.to_s)
91
- metadata['name'] = name
92
81
  # Don't look for existing if we should always create a new template
93
82
  existing = self.find_by(:name => name) unless options.delete(:build_new)
94
83
  # Don't update if the template already exists, unless we're told to
95
84
  return if !options.delete(:update) && existing
96
85
 
97
- template = existing || self.new
98
- template.sync_inputs(metadata.delete('template_inputs'))
99
- template.sync_foreign_input_sets(metadata.delete('foreign_input_sets'))
100
- template.sync_feature(metadata.delete('feature'))
101
- template.locked = false if options.delete(:force)
102
- template.assign_attributes(metadata.merge(:template => text.gsub(/<%\#.+?.-?%>\n?/m, '').strip).merge(options))
103
- template.assign_taxonomies if template.new_record?
86
+ template = existing || self.new(:name => name)
87
+ template.import_without_save(text, options)
104
88
  template
105
89
  end
106
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
107
90
  end
108
91
  end
109
92
 
@@ -215,9 +198,22 @@ class JobTemplate < ::Template
215
198
  end
216
199
  end
217
200
 
218
- def self.parse_metadata(template)
219
- match = template.match(/<%\#(.+?).-?%>/m)
220
- match.nil? ? {} : YAML.safe_load(match[1])
201
+ def import_custom_data(options)
202
+ sync_inputs(@importing_metadata['template_inputs'])
203
+ sync_foreign_input_sets(@importing_metadata['foreign_input_sets'])
204
+ sync_feature(@importing_metadata['feature'])
205
+
206
+ %w(job_category description_format provider_type).each do |attribute|
207
+ value = @importing_metadata[attribute]
208
+ self.public_send "#{attribute}=", value if @importing_metadata.key?(attribute)
209
+ end
210
+
211
+ # this should be moved to core but meanwhile we support default attribute here
212
+ # see http://projects.theforeman.org/issues/23426 for more details
213
+ self.default = options[:default] unless options[:default].nil?
214
+
215
+ # job templates have too long metadata, we remove them on parsing until it's stored in separate attribute
216
+ self.template = self.template.gsub(/<%\#.+?.-?%>\n?/m, '').strip
221
217
  end
222
218
 
223
219
  private
@@ -44,6 +44,15 @@ class RemoteExecutionProvider
44
44
  method
45
45
  end
46
46
 
47
+ def cleanup_working_dirs?(host)
48
+ setting = host_setting(host, :remote_execution_cleanup_working_dirs)
49
+ [true, 'true', 'True', 'TRUE', '1'].include?(setting)
50
+ end
51
+
52
+ def sudo_password(host)
53
+ host_setting(host, :remote_execution_sudo_password)
54
+ end
55
+
47
56
  def effective_interfaces(host)
48
57
  interfaces = []
49
58
  %w(execution primary provision).map do |flag|
@@ -70,7 +79,7 @@ class RemoteExecutionProvider
70
79
  end
71
80
 
72
81
  def host_setting(host, setting)
73
- host.params[setting.to_s] || Setting[setting]
82
+ host.host_param(setting.to_s) || Setting[setting]
74
83
  end
75
84
 
76
85
  def ssh_password(_host) end
@@ -1,8 +1,8 @@
1
1
  class Setting::RemoteExecution < Setting
2
2
 
3
- ::Setting::BLANK_ATTRS.concat %w{remote_execution_ssh_password remote_execution_ssh_key_passphrase}
3
+ ::Setting::BLANK_ATTRS.concat %w{remote_execution_ssh_password remote_execution_ssh_key_passphrase remote_execution_sudo_password}
4
4
 
5
- # rubocop:disable Metrics/MethodLength
5
+ # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
6
6
  def self.load_defaults
7
7
  # Check the table exists
8
8
  return unless super
@@ -39,6 +39,7 @@ class Setting::RemoteExecution < Setting
39
39
  N_('Effective User Method'),
40
40
  nil,
41
41
  { :collection => proc { Hash[SSHExecutionProvider::EFFECTIVE_USER_METHODS.map { |method| [method, method] }] } }),
42
+ self.set('remote_execution_sudo_password', N_("Sudo password"), '', N_("Sudo password"), nil, {:encrypted => true}),
42
43
  self.set('remote_execution_sync_templates',
43
44
  N_('Whether we should sync templates from disk when running db:seed.'),
44
45
  true,
@@ -63,12 +64,20 @@ class Setting::RemoteExecution < Setting
63
64
  nil,
64
65
  N_('Default SSH key passphrase'),
65
66
  nil,
66
- { :encrypted => true })
67
+ { :encrypted => true }),
68
+ self.set('remote_execution_workers_pool_size',
69
+ N_('Amount of workers in the pool to handle the execution of the remote execution jobs. Restart of the dynflowd/foreman-tasks service is required.'),
70
+ 5,
71
+ N_('Workers pool size')),
72
+ self.set('remote_execution_cleanup_working_dirs',
73
+ N_('When enabled, working directories will be removed after task completion. You may override this per host by setting a parameter called remote_execution_cleanup_working_dirs.'),
74
+ true,
75
+ N_('Cleanup working directories'))
67
76
  ].each { |s| self.create! s.update(:category => 'Setting::RemoteExecution') }
68
77
  end
69
78
 
70
79
  true
71
80
  end
72
81
  # rubocop:enable AbcSize
73
- # rubocop:enable Metrics/MethodLength
82
+ # rubocop:enable Metrics/MethodLength,Metrics/AbcSize
74
83
  end
@@ -4,6 +4,8 @@ class SSHExecutionProvider < RemoteExecutionProvider
4
4
  super.merge(:ssh_user => ssh_user(host),
5
5
  :effective_user => effective_user(template_invocation),
6
6
  :effective_user_method => effective_user_method(host),
7
+ :cleanup_working_dirs => cleanup_working_dirs?(host),
8
+ :sudo_password => sudo_password(host),
7
9
  :ssh_port => ssh_port(host))
8
10
  end
9
11
 
@@ -26,7 +28,7 @@ class SSHExecutionProvider < RemoteExecutionProvider
26
28
  private
27
29
 
28
30
  def ssh_user(host)
29
- host.params['remote_execution_ssh_user']
31
+ host.host_param('remote_execution_ssh_user')
30
32
  end
31
33
 
32
34
  def ssh_port(host)
@@ -93,6 +93,7 @@
93
93
  <div class="advanced hidden">
94
94
  <%= password_f f, :password, :placeholder => '*****', :label => _('Password'), :label_help => N_('Password is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %>
95
95
  <%= password_f f, :key_passphrase, :placeholder => '*****', :label => _('Private key passphrase'), :label_help => N_('Key passhprase is only applicable for SSH provider. Other providers ignore this field. <br> Passphrase is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %>
96
+ <%= password_f f, :sudo_password, :placeholder => '*****', :label => _('Sudo password'), :label_help => N_('Sudo password is only applicable for SSH provider. Other providers ignore this field. <br> Password is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %>
96
97
  </div>
97
98
 
98
99
  <div class="advanced hidden">
@@ -1,7 +1,9 @@
1
1
  <% title @job_invocation.description, trunc_with_tooltip(@job_invocation.description, 120) %>
2
2
  <% stylesheet 'foreman_remote_execution/job_invocations' %>
3
3
  <% javascript 'charts', 'foreman_remote_execution/template_invocation' %>
4
- <%= javascript_include_tag *webpack_asset_paths('remoteexecution', :extension => 'js'), "data-turbolinks-track" => true, 'defer' => 'defer' %>
4
+ <%= javascript_include_tag *webpack_asset_paths('foreman_remote_execution', :extension => 'js'), "data-turbolinks-track" => true, 'defer' => 'defer' %>
5
+
6
+ <%= breadcrumbs name_field: 'description' %>
5
7
 
6
8
  <% if @job_invocation.task %>
7
9
  <% title_actions(button_group(job_invocation_task_buttons(@job_invocation.task))) %>
@@ -1,6 +1,19 @@
1
1
  <%= javascript 'lookup_keys' %>
2
2
  <%= javascript 'foreman_remote_execution/template_input' %>
3
3
 
4
+ <%= breadcrumbs(
5
+ items: [
6
+ {
7
+ caption: _('Job Templates'),
8
+ url: url_for(job_templates_path)
9
+ },
10
+ {
11
+ caption: _('Edit %s' % @template.to_label)
12
+ }
13
+ ]
14
+ )
15
+ %>
16
+
4
17
  <% title _("Edit Job Template") %>
5
18
 
6
19
  <%= render :partial => 'form' %>
@@ -7,7 +7,7 @@
7
7
  link_to_function(_('Import'), 'show_import_job_template_modal();', :class => 'btn btn-default'),
8
8
  new_link(_("New Job Template"))) %>
9
9
 
10
- <table class="<%= table_css_classes('table-two-pane table-fixed') %>">
10
+ <table class="<%= table_css_classes('table-fixed') %>">
11
11
  <thead>
12
12
  <tr>
13
13
  <th class="col-md-6"><%= sort :name, :as => s_("JobTemplate|Name") %></th>
@@ -0,0 +1,5 @@
1
+ class AddSudoPasswordToJobInvocation < ActiveRecord::Migration[4.2]
2
+ def change
3
+ add_column :job_invocations, :sudo_password, :string
4
+ end
5
+ end
@@ -24,9 +24,9 @@ Gem::Specification.new do |s|
24
24
  s.extra_rdoc_files = Dir['README*', 'LICENSE']
25
25
 
26
26
  s.add_dependency 'deface'
27
- s.add_dependency 'dynflow', '>= 1.0.0', '< 2.0.0'
27
+ s.add_dependency 'dynflow', '>= 1.0.1', '< 2.0.0'
28
28
  s.add_dependency 'foreman_remote_execution_core'
29
- s.add_dependency 'foreman-tasks', '~> 0.12'
29
+ s.add_dependency 'foreman-tasks', '~> 0.13'
30
30
 
31
31
  s.add_development_dependency 'factory_bot_rails', '~> 4.8.0'
32
32
  s.add_development_dependency 'rubocop'
@@ -1,6 +1,8 @@
1
1
  require 'foreman_remote_execution_core'
2
2
 
3
3
  module ForemanRemoteExecution
4
+ DYNFLOW_QUEUE = :remote_execution
5
+
4
6
  class Engine < ::Rails::Engine
5
7
  engine_name 'foreman_remote_execution'
6
8
 
@@ -26,6 +28,7 @@ module ForemanRemoteExecution
26
28
 
27
29
  initializer 'foreman_remote_execution.require_dynflow', :before => 'foreman_tasks.initialize_dynflow' do |app|
28
30
  ForemanTasks.dynflow.require!
31
+ ForemanTasks.dynflow.config.queues.add(DYNFLOW_QUEUE, :pool_size => Setting['remote_execution_workers_pool_size']) if Setting.table_exists? rescue(false)
29
32
  ForemanTasks.dynflow.config.eager_load_paths << File.join(ForemanRemoteExecution::Engine.root, 'app/lib/actions')
30
33
  end
31
34
 
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '1.5.1'.freeze
2
+ VERSION = '1.5.2'.freeze
3
3
  end
@@ -98,7 +98,7 @@ module Api
98
98
 
99
99
  test 'should provide output for delayed task' do
100
100
  host = @invocation.template_invocations_hosts.first
101
- ForemanTasks::Task.any_instance.expects(:delayed?).returns(true)
101
+ ForemanTasks::Task.any_instance.expects(:scheduled?).returns(true)
102
102
  get :output, params: { :job_invocation_id => @invocation.id, :host_id => host.id }
103
103
  result = ActiveSupport::JSON.decode(@response.body)
104
104
  assert_equal result['delayed'], true
@@ -0,0 +1,63 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module ForemanRemoteExecution
4
+ class RunHostJobTest < ActiveSupport::TestCase
5
+ include Dynflow::Testing
6
+
7
+ subject { create_action(Actions::RemoteExecution::RunHostJob) }
8
+ let(:host) { FactoryBot.create(:host, :with_execution) }
9
+
10
+ before do
11
+ subject.stubs(:input).returns({ host: { id: host.id } })
12
+ Host.expects(:find).with(host.id).returns(host)
13
+ end
14
+
15
+ describe '#finalize' do
16
+ describe 'updates the host status' do
17
+ before do
18
+ subject.expects(:check_exit_status).returns(nil)
19
+ end
20
+
21
+ context 'with stubbed status' do
22
+ let(:stub_status) do
23
+ status = HostStatus::ExecutionStatus.new
24
+ status.stubs(:save!).returns(true)
25
+ status
26
+ end
27
+
28
+ before do
29
+ host.expects(:execution_status_object).returns(stub_status)
30
+ end
31
+
32
+ context 'exit_status is 0' do
33
+ it 'updates the host status to OK' do
34
+ subject.stubs(:exit_status).returns(0)
35
+ stub_status.expects(:"status=").with(HostStatus::ExecutionStatus::OK)
36
+ subject.finalize
37
+ end
38
+ end
39
+
40
+ context 'exit_status is NOT 0' do
41
+ it 'updates the host status to ERROR' do
42
+ subject.stubs(:exit_status).returns(1)
43
+ stub_status.expects(:"status=").with(HostStatus::ExecutionStatus::ERROR)
44
+ subject.finalize
45
+ end
46
+ end
47
+ end
48
+
49
+ context 'host has no execution status yet' do
50
+ before do
51
+ assert_nil host.execution_status_object
52
+ subject.stubs(:exit_status).returns(0)
53
+ end
54
+
55
+ it 'creates a new status' do
56
+ subject.finalize
57
+ refute_nil host.execution_status_object
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -14,6 +14,7 @@ module ForemanRemoteExecution
14
14
  invocation.description = 'Some short description'
15
15
  invocation.password = 'changeme'
16
16
  invocation.key_passphrase = 'changemetoo'
17
+ invocation.sudo_password = 'sudopassword'
17
18
  invocation.save
18
19
  end
19
20
  end
@@ -20,21 +20,21 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
20
20
  end
21
21
 
22
22
  it 'has ssh user in the parameters' do
23
- host.params['remote_execution_ssh_user'].must_equal Setting[:remote_execution_ssh_user]
23
+ host.host_param('remote_execution_ssh_user').must_equal Setting[:remote_execution_ssh_user]
24
24
  end
25
25
 
26
26
  it 'can override ssh user' do
27
27
  host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_user', :value => 'amy')
28
- host.params['remote_execution_ssh_user'].must_equal 'amy'
28
+ host.host_param('remote_execution_ssh_user').must_equal 'amy'
29
29
  end
30
30
 
31
31
  it 'has effective user method in the parameters' do
32
- host.params['remote_execution_effective_user_method'].must_equal Setting[:remote_execution_effective_user_method]
32
+ host.host_param('remote_execution_effective_user_method').must_equal Setting[:remote_execution_effective_user_method]
33
33
  end
34
34
 
35
35
  it 'can override effective user method' do
36
36
  host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_effective_user_method', :value => 'su')
37
- host.params['remote_execution_effective_user_method'].must_equal 'su'
37
+ host.host_param('remote_execution_effective_user_method').must_equal 'su'
38
38
  end
39
39
 
40
40
  it 'has ssh keys in the parameters' do
@@ -501,6 +501,18 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
501
501
  end
502
502
  end
503
503
 
504
+ describe '#sudo_password' do
505
+ let(:sudo_password) { 'password' }
506
+ let(:params) do
507
+ { :job_invocation => { :sudo_password => sudo_password }}
508
+ end
509
+
510
+ it 'sets the sudo password properly' do
511
+ composer
512
+ composer.job_invocation.sudo_password.must_equal sudo_password
513
+ end
514
+ end
515
+
504
516
  describe '#targeting' do
505
517
  it 'triggers targeting on job_invocation' do
506
518
  composer
@@ -72,15 +72,21 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
72
72
 
73
73
  describe 'ssh user' do
74
74
  it 'uses the remote_execution_ssh_user on the host param' do
75
- host.params['remote_execution_ssh_user'] = 'my user'
76
75
  host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_user', :value => 'my user')
77
76
  proxy_options[:ssh_user].must_equal 'my user'
78
77
  end
79
78
  end
80
79
 
80
+ describe 'sudo password' do
81
+ it 'uses the remote_execution_sudo_password on the host param' do
82
+ host.params['remote_execution_sudo_password'] = 'mypassword'
83
+ host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_sudo_password', :value => 'mypassword')
84
+ proxy_options[:sudo_password].must_equal 'mypassword'
85
+ end
86
+ end
87
+
81
88
  describe 'sudo' do
82
89
  it 'uses the remote_execution_ssh_user on the host param' do
83
- host.params['remote_execution_effective_user_method'] = 'sudo'
84
90
  method_param = FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_effective_user_method', :value => 'sudo')
85
91
  host.host_parameters << method_param
86
92
  proxy_options[:effective_user_method].must_equal 'sudo'
@@ -112,6 +118,25 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
112
118
  end
113
119
  end
114
120
 
121
+ describe 'cleanup working directories setting' do
122
+ before do
123
+ Setting[:remote_execution_cleanup_working_dirs] = false
124
+ end
125
+
126
+ it 'updates the value via settings' do
127
+ proxy_options[:cleanup_working_dirs].must_equal false
128
+ end
129
+ end
130
+
131
+ describe 'cleanup working directories from parameters' do
132
+ it 'takes the value from host parameters' do
133
+ host.params['remote_execution_cleanup_working_dirs'] = 'false'
134
+ host.host_parameters << FactoryBot.build(:host_parameter, :name => 'remote_execution_cleanup_working_dirs', :value => 'false')
135
+ host.clear_host_parameters_cache!
136
+ proxy_options[:cleanup_working_dirs].must_equal false
137
+ end
138
+ end
139
+
115
140
  describe '#find_ip_or_hostname' do
116
141
  let(:host) do
117
142
  FactoryBot.create(:host) do |host|
@@ -139,7 +164,7 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
139
164
  end
140
165
 
141
166
  it 'gets ip from flagged interfaces' do
142
- host.params['remote_execution_connect_by_ip'] = true
167
+ host.host_params['remote_execution_connect_by_ip'] = true
143
168
  # no ip address set on relevant interface - fallback to fqdn
144
169
  SSHExecutionProvider.find_ip_or_hostname(host).must_equal 'somehost.somedomain.org'
145
170
 
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: 1.5.1
4
+ version: 1.5.2
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: 2018-04-12 00:00:00.000000000 Z
11
+ date: 2018-05-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deface
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 1.0.0
33
+ version: 1.0.1
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
36
  version: 2.0.0
@@ -40,7 +40,7 @@ dependencies:
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: 1.0.0
43
+ version: 1.0.1
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
46
  version: 2.0.0
@@ -64,14 +64,14 @@ dependencies:
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '0.12'
67
+ version: '0.13'
68
68
  type: :runtime
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '0.12'
74
+ version: '0.13'
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: factory_bot_rails
77
77
  requirement: !ruby/object:Gem::Requirement
@@ -189,7 +189,6 @@ files:
189
189
  - app/models/job_invocation_task_group.rb
190
190
  - app/models/job_template.rb
191
191
  - app/models/job_template_effective_user.rb
192
- - app/models/job_template_importer.rb
193
192
  - app/models/remote_execution_feature.rb
194
193
  - app/models/remote_execution_provider.rb
195
194
  - app/models/setting/remote_execution.rb
@@ -318,6 +317,7 @@ files:
318
317
  - db/migrate/20180202072115_add_notification_builder_to_remote_execution_feature.rb
319
318
  - db/migrate/20180202123215_add_feature_id_to_job_invocation.rb
320
319
  - db/migrate/20180226095631_change_task_id_to_uuid.rb
320
+ - db/migrate/20180411160809_add_sudo_password_to_job_invocation.rb
321
321
  - db/seeds.d/50-notification_blueprints.rb
322
322
  - db/seeds.d/60-ssh_proxy_feature.rb
323
323
  - db/seeds.d/70-job_templates.rb
@@ -364,6 +364,7 @@ files:
364
364
  - test/functional/api/v2/template_inputs_controller_test.rb
365
365
  - test/functional/job_invocations_controller_test.rb
366
366
  - test/test_plugin_helper.rb
367
+ - test/unit/actions/run_host_job_test.rb
367
368
  - test/unit/actions/run_hosts_job_test.rb
368
369
  - test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb
369
370
  - test/unit/concerns/host_extensions_test.rb
@@ -373,7 +374,6 @@ files:
373
374
  - test/unit/job_invocation_composer_test.rb
374
375
  - test/unit/job_invocation_test.rb
375
376
  - test/unit/job_template_effective_user_test.rb
376
- - test/unit/job_template_importer_test.rb
377
377
  - test/unit/job_template_test.rb
378
378
  - test/unit/remote_execution_feature_test.rb
379
379
  - test/unit/remote_execution_provider_test.rb
@@ -410,7 +410,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
410
410
  version: '0'
411
411
  requirements: []
412
412
  rubyforge_project:
413
- rubygems_version: 2.6.12
413
+ rubygems_version: 2.7.3
414
414
  signing_key:
415
415
  specification_version: 4
416
416
  summary: A plugin bringing remote execution to the Foreman, completing the config
@@ -426,6 +426,7 @@ test_files:
426
426
  - test/functional/api/v2/template_inputs_controller_test.rb
427
427
  - test/functional/job_invocations_controller_test.rb
428
428
  - test/test_plugin_helper.rb
429
+ - test/unit/actions/run_host_job_test.rb
429
430
  - test/unit/actions/run_hosts_job_test.rb
430
431
  - test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb
431
432
  - test/unit/concerns/host_extensions_test.rb
@@ -435,7 +436,6 @@ test_files:
435
436
  - test/unit/job_invocation_composer_test.rb
436
437
  - test/unit/job_invocation_test.rb
437
438
  - test/unit/job_template_effective_user_test.rb
438
- - test/unit/job_template_importer_test.rb
439
439
  - test/unit/job_template_test.rb
440
440
  - test/unit/remote_execution_feature_test.rb
441
441
  - test/unit/remote_execution_provider_test.rb
@@ -1,36 +0,0 @@
1
- # This class is a shim to handle the importing of templates via the
2
- # foreman_templates plugin. It expects a method like
3
- # def import(name, text, metadata)
4
- # but REx already has an import! method, so this class provides the
5
- # translation layer.
6
-
7
- class JobTemplateImporter
8
- def self.import!(name, text, metadata, force = false)
9
- skip = skip_locked(name, force)
10
- return skip if skip
11
-
12
- template = JobTemplate.import_parsed(name, text, metadata, :update => true, :force => force)
13
- c_or_u = template.new_record? ? 'Created' : 'Updated'
14
-
15
- result = " #{c_or_u} Template #{id_string template}:#{name}"
16
- { :old => template.template_was,
17
- :new => template.template,
18
- :status => template.save,
19
- :result => result}
20
- end
21
-
22
- def self.skip_locked(name, force)
23
- template = JobTemplate.find_by :name => name
24
-
25
- if template && template.locked? && !template.new_record? && !force
26
- { :old => template.template_was,
27
- :new => template.template,
28
- :status => false,
29
- :result => "Skipping Template #{id_string template}:#{name} - template is locked" }
30
- end
31
- end
32
-
33
- def self.id_string(template)
34
- template ? template.id.to_s : ''
35
- end
36
- end
@@ -1,66 +0,0 @@
1
- require 'test_plugin_helper'
2
-
3
- class JobTemplateImporterTest < ActiveSupport::TestCase
4
- context 'importing a new template' do
5
- # JobTemplate tests handle most of this, we just check that the shim
6
- # correctly loads a template returns a hash
7
- let(:remote_execution_feature) do
8
- FactoryBot.create(:remote_execution_feature)
9
- end
10
-
11
- let(:result) do
12
- name = 'Community Service Restart'
13
- metadata = {
14
- 'model' => 'JobTemplate',
15
- 'kind' => 'job_template',
16
- 'name' => 'Service Restart',
17
- 'job_category' => 'Service Restart',
18
- 'provider_type' => 'SSH',
19
- 'feature' => remote_execution_feature.label,
20
- 'template_inputs' => [
21
- { 'name' => 'service_name', 'input_type' => 'user', 'required' => true },
22
- { 'name' => 'verbose', 'input_type' => 'user' }
23
- ]
24
- }
25
- text = <<-END_TEMPLATE.strip_heredoc
26
- <%#
27
- #{YAML.dump(metadata)}
28
- %>
29
-
30
- service <%= input("service_name") %> restart
31
- END_TEMPLATE
32
-
33
- JobTemplateImporter.import!(name, text, metadata)
34
- end
35
-
36
- let(:template) { JobTemplate.find_by name: 'Community Service Restart' }
37
-
38
- it 'returns a valid foreman_templates hash' do
39
- result[:status].must_equal true
40
- result[:result].must_equal ' Created Template :Community Service Restart'
41
- result[:old].must_be_nil
42
- result[:new].must_equal template.template.squish
43
- end
44
- end
45
-
46
- context 'updating locked template' do
47
- it 'does not update locked template' do
48
- name = 'Locked job template'
49
- template = FactoryBot.create(:job_template, :locked => true, :name => name)
50
- res = JobTemplateImporter.import!(name, 'some text', 'metadata')
51
- assert_equal "Skipping Template #{template.id}:#{template.name} - template is locked", res[:result]
52
- end
53
-
54
- it 'updates locked template' do
55
- name = 'Locked job template again'
56
- metadata = {
57
- 'model' => 'JobTemplate',
58
- 'kind' => 'job_template',
59
- 'name' => name
60
- }
61
- template = FactoryBot.create(:job_template, :locked => true, :name => name)
62
- res = JobTemplateImporter.import!(name, 'some text', metadata, true)
63
- assert_equal " Updated Template #{template.id}:Locked job template again", res[:result]
64
- end
65
- end
66
- end