foreman_remote_execution 1.5.1 → 1.5.2

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