foreman_remote_execution 1.5.4 → 1.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d911acff83175160207a45f2ca0ba34827beff57
4
- data.tar.gz: 7ed92c4c5653b5317d17aa6c470ba361e7ee26b1
2
+ SHA256:
3
+ metadata.gz: 9494abeb23995efb0f837561fd1b1bd84fbd7ef37060081ea573d2dde16bd74b
4
+ data.tar.gz: afbf23769a88fecbbcb7197249b2261a046b568be38699c35a12210ab6cb13e5
5
5
  SHA512:
6
- metadata.gz: ba51f41db0f75a572099ab7bf4d69f926df0e942f5ded5b21f8111caa14e2ed4f2d1d7d5119089c4c888ca00d079113f15575933552d60d13a8daf8633ad8b39
7
- data.tar.gz: f0bb2bdb7f28f155c2846442e7b7661e834c15beaf0f49aebe405ed39ab217d65db8c825d58fc11974394212e6362a67a17ccf272cd03054643e23f06b6b9248
6
+ metadata.gz: 28dd96a2c75c9e94f71a6613fac13ecbf7938d46fdfc0d5208833073149a3e60c84de6a7ee357ca22318abae29a7deb85afb77377ff462ee71aade08cf7ec0ca
7
+ data.tar.gz: 5d012baa162db923230e7d7c0f0dc03a315075ce79f3bc64b8cc1236c328061c2ff839f97c56849157c303fc5e758376ecab15d65137cf206aabe6159d7025cf
@@ -82,17 +82,15 @@ module Api
82
82
  return
83
83
  end
84
84
  task = @nested_obj.sub_task_for_host(@host)
85
- refresh = task.pending?
86
- since = params[:since].to_f if params[:since].present?
85
+ refresh = true
86
+ line_sets = []
87
87
 
88
- line_sets = task.main_action.live_output
89
- line_sets = line_sets.drop_while { |o| o['timestamp'].to_f <= since } if since
90
-
91
- if line_sets.blank?
92
- render :json => {:refresh => refresh, :output => []}
93
- else
94
- render :json => {:refresh => refresh, :output => line_sets }
88
+ if task
89
+ refresh = task.pending?
90
+ line_sets = output_lines_since task, params[:since]
95
91
  end
92
+
93
+ render :json => { :refresh => refresh, :output => line_sets || [] }
96
94
  end
97
95
 
98
96
  api :POST, '/job_invocations/:id/cancel', N_('Cancel job invocation')
@@ -113,9 +111,14 @@ module Api
113
111
  param :failed_only, :bool
114
112
  def rerun
115
113
  composer = JobInvocationComposer.from_job_invocation(@job_invocation, params)
116
- composer.trigger!
117
- @job_invocation = composer.job_invocation
118
- process_response @job_invocation
114
+ if composer.rerun_possible?
115
+ composer.trigger!
116
+ @job_invocation = composer.job_invocation
117
+ process_response @job_invocation
118
+ else
119
+ render :json => { :error => _('Could not rerun job %{id} because its template could not be found') % { :id => composer.reruns } },
120
+ :status => 404
121
+ end
119
122
  end
120
123
 
121
124
  private
@@ -163,6 +166,13 @@ module Api
163
166
  job_invocation_params[:inputs].to_hash
164
167
  )
165
168
  end
169
+
170
+ def output_lines_since(task, time)
171
+ since = time.to_f if time.present?
172
+ line_sets = task.main_action.live_output
173
+ line_sets = line_sets.drop_while { |o| o['timestamp'].to_f <= since } if since
174
+ line_sets
175
+ end
166
176
  end
167
177
  end
168
178
  end
@@ -19,10 +19,15 @@ class JobTemplatesController < ::TemplatesController
19
19
  host = params[:preview_host_id].present? ? base.find(params[:preview_host_id]) : base.first
20
20
  @template.template = params[:template]
21
21
  renderer = InputTemplateRenderer.new(@template, host)
22
- if (output = renderer.preview)
22
+ output = renderer.preview
23
+ if output
23
24
  render :plain => output
24
25
  else
25
- render :status => 406, :text => _('Problem with previewing the template: %{error}. Note that you must save template input changes before you try to preview it.' % {:error => renderer.error_message})
26
+ render status: :not_acceptable,
27
+ plain: _(
28
+ 'Problem with previewing the template: %{error}. Note that you must save template input changes before you try to preview it.' %
29
+ {:error => renderer.error_message}
30
+ )
26
31
  end
27
32
  end
28
33
 
@@ -161,7 +161,7 @@ module RemoteExecutionHelper
161
161
 
162
162
  def preview_box(template_invocation, target)
163
163
  renderer = InputTemplateRenderer.new(template_invocation.template, target, template_invocation)
164
- if (preview = renderer.preview)
164
+ if (preview = (load_template_from_task(template_invocation, target) || renderer.preview))
165
165
  content_tag :pre, preview
166
166
  elsif target.nil?
167
167
  alert :text => _('Could not render the preview because no host matches the search query.'),
@@ -234,4 +234,10 @@ module RemoteExecutionHelper
234
234
  end
235
235
  end
236
236
  end
237
+
238
+ def load_template_from_task(template_invocation, target)
239
+ task = template_invocation.job_invocation.sub_task_for_host(target)
240
+ return if task.nil?
241
+ task.execution_plan.actions[1].try(:input) { |input| input['script'] }
242
+ end
237
243
  end
@@ -58,7 +58,7 @@ class InputTemplateRenderer
58
58
  def render_template(template_name, input_values = {}, options = {})
59
59
  options.assert_valid_keys(:with_foreign_input_set)
60
60
  with_foreign_input_set = options.fetch(:with_foreign_input_set, true)
61
- template = @template.class.authorized("view_#{@template.class.to_s.underscore}").find_by(:name => template_name)
61
+ template = @template.class.authorized("view_#{@template.class.to_s.underscore.pluralize}").find_by(:name => template_name)
62
62
  unless template
63
63
  self.error_message = _('included template \'%s\' not found') % template_name
64
64
  raise error_message
@@ -186,7 +186,8 @@ class JobInvocationComposer
186
186
  :concurrency_control => concurrency_control_params,
187
187
  :execution_timeout_interval => job_invocation.execution_timeout_interval,
188
188
  :remote_execution_feature_id => job_invocation.remote_execution_feature_id,
189
- :template_invocations => template_invocations_params }.with_indifferent_access
189
+ :template_invocations => template_invocations_params,
190
+ :reruns => job_invocation.id }.with_indifferent_access
190
191
  end
191
192
 
192
193
  private
@@ -297,6 +298,7 @@ class JobInvocationComposer
297
298
  end
298
299
 
299
300
  attr_accessor :params, :job_invocation, :host_ids, :search_query
301
+ attr_reader :reruns
300
302
  delegate :job_category, :remote_execution_feature_id, :pattern_template_invocations, :template_invocations, :targeting, :triggering, :to => :job_invocation
301
303
 
302
304
  def initialize(params, set_defaults = false)
@@ -304,6 +306,7 @@ class JobInvocationComposer
304
306
  @set_defaults = set_defaults
305
307
  @job_invocation = JobInvocation.new
306
308
  @job_invocation.task_group = JobInvocationTaskGroup.new
309
+ @reruns = params[:reruns]
307
310
  compose
308
311
 
309
312
  @host_ids = validate_host_ids(params[:host_ids])
@@ -342,6 +345,8 @@ class JobInvocationComposer
342
345
  job_invocation.key_passphrase = params[:key_passphrase]
343
346
  job_invocation.sudo_password = params[:sudo_password]
344
347
 
348
+ job_invocation.job_category = nil unless rerun_possible?
349
+
345
350
  self
346
351
  end
347
352
  # rubocop:enable Metrics/AbcSize
@@ -470,6 +475,10 @@ class JobInvocationComposer
470
475
  job_invocation.pattern_template_invocations.map(&:template_id)
471
476
  end
472
477
 
478
+ def rerun_possible?
479
+ !(reruns && job_invocation.pattern_template_invocations.empty?)
480
+ end
481
+
473
482
  private
474
483
 
475
484
  # builds input values for a given templates id based on params
@@ -1,12 +1,14 @@
1
1
  class JobTemplate < ::Template
2
2
  audited
3
3
  include ::Exportable
4
+ include ::TemplateTax
4
5
 
5
6
  class NonUniqueInputsError < Foreman::Exception
6
7
  end
7
8
 
8
9
  attr_exportable :job_category, :description_format, :template_inputs,
9
- :foreign_input_sets, :provider_type, :kind => ->(template) { template.class.name.underscore }
10
+ :foreign_input_sets, :provider_type,
11
+ { :kind => ->(template) { template.class.name.underscore } }.merge(taxonomy_exportable)
10
12
 
11
13
  include Authorizable
12
14
  extend FriendlyId
@@ -134,7 +136,7 @@ class JobTemplate < ::Template
134
136
 
135
137
  def generate_description_format
136
138
  if description_format.blank?
137
- generated_description = '%{job_category}'
139
+ generated_description = '%{template_name}'
138
140
  unless template_inputs_with_foreign.empty?
139
141
  inputs = template_inputs_with_foreign.map(&:name).map { |name| %{#{name}="%{#{name}}"} }.join(' ')
140
142
  generated_description << " with inputs #{inputs}"
@@ -6,7 +6,15 @@
6
6
 
7
7
  <%= form_for @composer.job_invocation, :html => {'data-refresh-url' => refresh_job_invocations_path, :id => 'job_invocation_form'} do |f| %>
8
8
 
9
- <%= selectable_f f, :job_category, @composer.available_job_categories, {}, :label => _('Job category') %>
9
+ <% unless @composer.rerun_possible? %>
10
+ <%= alert :text => _('Could not rerun job %{id} because its template could not be found') % { :id => @composer.reruns },
11
+ :class => 'alert alert-block alert-danger has-error',
12
+ :close => false %>
13
+ <% end %>
14
+
15
+ <%= selectable_f f, :job_category, @composer.available_job_categories,
16
+ { :selected => @composer.job_invocation.job_category, :include_blank => @composer.job_invocation.job_category.nil? },
17
+ :label => _('Job category') %>
10
18
  <%= f.hidden_field(:remote_execution_feature_id, :value => @composer.remote_execution_feature_id) %>
11
19
 
12
20
  <% selected_templates_per_provider = {} %>
@@ -116,5 +124,5 @@
116
124
 
117
125
  <%= render :partial => 'preview_hosts_modal' %>
118
126
 
119
- <%= submit_or_cancel f, false, :cancel_path => job_invocations_path %>
127
+ <%= submit_or_cancel f, false, :cancel_path => job_invocations_path, :disabled => !@composer.rerun_possible? %>
120
128
  <% end %>
@@ -1,5 +1,5 @@
1
1
  <div class="tab-pane" id="rex_proxies">
2
2
  <%= fields_for :subnet do |f| %>
3
- <%= multiple_selects f, :remote_execution_proxies, SmartProxy.authorized.with_features(*RemoteExecutionProvider.provider_names), @subnet.remote_execution_proxy_ids, {:label => _("Proxies"), :help_inline => _("Select as many remote execution proxies as applicable for this subnet. When multiple proxies with the same provider are added, actions will be load balanced among them.")} %>
3
+ <%= multiple_selects f, :remote_execution_proxies, SmartProxy.authorized.with_features(*RemoteExecutionProvider.provider_names).distinct, @subnet.remote_execution_proxy_ids, {:label => _("Proxies"), :help_inline => _("Select as many remote execution proxies as applicable for this subnet. When multiple proxies with the same provider are added, actions will be load balanced among them.")} %>
4
4
  <% end %>
5
5
  </div>
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '1.5.4'.freeze
2
+ VERSION = '1.5.5'.freeze
3
3
  end
@@ -1,14 +1,12 @@
1
1
  FactoryBot.define do
2
-
3
- factory :job_template do |f|
4
- f.sequence(:name) { |n| "Job template #{n}" }
5
- sequence(:job_category) { |n| "job name #{n}" }
6
- f.template 'id'
7
- f.provider_type 'SSH'
2
+ factory :job_template do
3
+ sequence(:name) { |n| "Job template #{n}" }
4
+ sequence(:job_category) { |n| "Job name #{n}" }
5
+ template 'id'
6
+ provider_type 'SSH'
8
7
  organizations { [Organization.find_by(name: 'Organization 1')] } if SETTINGS[:organizations_enabled]
9
8
  locations { [Location.find_by(name: 'Location 1')] } if SETTINGS[:locations_enabled]
10
9
 
11
-
12
10
  trait :with_input do
13
11
  after(:build) do |template, evaluator|
14
12
  template.template_inputs << FactoryBot.build(:template_input)
@@ -6,6 +6,11 @@ module Api
6
6
  setup do
7
7
  @invocation = FactoryBot.create(:job_invocation, :with_template, :with_task)
8
8
  @template = FactoryBot.create(:job_template, :with_input)
9
+
10
+ # Without this the template in template_invocations and in pattern_template_invocations
11
+ # would belong to different job_categories, causing trouble when trying to rerun
12
+ @invocation.job_category = @invocation.pattern_template_invocations.first.template.job_category
13
+ @invocation.save!
9
14
  end
10
15
 
11
16
  test 'should get index' do
@@ -107,6 +112,16 @@ module Api
107
112
  assert_response :success
108
113
  end
109
114
 
115
+ test 'should provide empty output for host which does not have a task yet' do
116
+ host = @invocation.template_invocations_hosts.first
117
+ JobInvocation.any_instance.expects(:sub_task_for_host).returns(nil)
118
+ get :output, params: { :job_invocation_id => @invocation.id, :host_id => host.id }
119
+ result = ActiveSupport::JSON.decode(@response.body)
120
+ assert_equal result['refresh'], true
121
+ assert_equal result['output'], []
122
+ assert_response :success
123
+ end
124
+
110
125
  test 'should cancel a job' do
111
126
  @invocation.task.expects(:cancellable?).returns(true)
112
127
  @invocation.task.expects(:cancel).returns(true)
@@ -168,6 +183,8 @@ module Api
168
183
 
169
184
  test 'should rerun failed only' do
170
185
  @invocation = FactoryBot.create(:job_invocation, :with_template, :with_failed_task)
186
+ @invocation.job_category = @invocation.pattern_template_invocations.first.template.job_category
187
+ @invocation.save!
171
188
  JobInvocation.any_instance.expects(:generate_description)
172
189
  JobInvocationComposer.any_instance
173
190
  .expects(:validate_job_category)
@@ -181,6 +198,13 @@ module Api
181
198
  targeting.user_id.must_equal users(:admin).id
182
199
  targeting.search_query.must_equal "name ^ (#{hostnames.join(',')})"
183
200
  end
201
+
202
+ test 'should return 404 if template is not found' do
203
+ @invocation.job_category = 'Missing category'
204
+ @invocation.save!
205
+ post :rerun, params: { :id => @invocation.id }
206
+ assert_response 404
207
+ end
184
208
  end
185
209
  end
186
210
  end
@@ -92,6 +92,7 @@ module Api
92
92
 
93
93
  test 'should export template' do
94
94
  get :export, params: { :id => @template.to_param }
95
+ User.current = users(:admin)
95
96
  assert_equal @response.body, @template.to_erb
96
97
  assert_response :success
97
98
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+
5
+ class JobTemplatesControllerTest < ActionController::TestCase
6
+ context '#preview' do
7
+ let(:template) { FactoryBot.create(:job_template) }
8
+ let(:host) { FactoryBot.create(:host, :managed) }
9
+
10
+ test 'should render a preview version of a template' do
11
+ post :preview, params: { job_template: template.to_param, template: 'uptime' }, session: set_session_user
12
+ assert_response :success
13
+ end
14
+
15
+ test 'should render a preview version of a template for a specific host' do
16
+ post :preview, params: {
17
+ job_template: template.to_param,
18
+ template: '<%= @host.name %>',
19
+ preview_host_id: host.id
20
+ }, session: set_session_user
21
+ assert_response :success
22
+ assert_equal host.name, @response.body
23
+ end
24
+
25
+ test 'should render a error message when template has errors' do
26
+ InputTemplateRenderer.any_instance.stubs(:render).returns(false)
27
+ post :preview, params: { job_template: template.to_param }, session: set_session_user
28
+ assert_response :not_acceptable
29
+ end
30
+ end
31
+ end
@@ -156,6 +156,24 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
156
156
  end
157
157
  end
158
158
 
159
+ describe '#rerun_possible?' do
160
+ it 'is true when not rerunning' do
161
+ composer.must_be :rerun_possible?
162
+ end
163
+
164
+ it 'is true when rerunning with pattern tempalte invocations' do
165
+ composer.expects(:reruns).returns(1)
166
+ composer.job_invocation.expects(:pattern_template_invocations).returns([1])
167
+ composer.must_be :rerun_possible?
168
+ end
169
+
170
+ it 'is false when rerunning without pattern template invocations' do
171
+ composer.expects(:reruns).returns(1)
172
+ composer.job_invocation.expects(:pattern_template_invocations).returns([])
173
+ composer.wont_be :rerun_possible?
174
+ end
175
+ end
176
+
159
177
  describe '#selected_job_templates' do
160
178
  it 'returns no template if none was selected through params' do
161
179
  composer.selected_job_templates.must_be_empty
@@ -44,14 +44,14 @@ class JobTemplateTest < ActiveSupport::TestCase
44
44
  end
45
45
 
46
46
  it 'uses the job name as description_format if not set or blank and has no inputs' do
47
- minimal_template.generate_description_format.must_equal '%{job_category}'
47
+ minimal_template.generate_description_format.must_equal '%{template_name}'
48
48
  minimal_template.description_format = ''
49
- minimal_template.generate_description_format.must_equal '%{job_category}'
49
+ minimal_template.generate_description_format.must_equal '%{template_name}'
50
50
  end
51
51
 
52
52
  it 'generates the description_format if not set or blank and has inputs' do
53
53
  input_name = template.template_inputs.first.name
54
- expected_result = %(%{job_category} with inputs #{input_name}="%{#{input_name}}")
54
+ expected_result = %(%{template_name} with inputs #{input_name}="%{#{input_name}}")
55
55
  template.generate_description_format.must_equal expected_result
56
56
  template.description_format = ''
57
57
  template.generate_description_format.must_equal expected_result
@@ -267,6 +267,11 @@ class JobTemplateTest < ActiveSupport::TestCase
267
267
  imported.name.must_equal old_name
268
268
  imported.template_inputs.first.to_export.must_equal exportable_template.template_inputs.first.to_export
269
269
  end
270
+
271
+ it 'has taxonomies in metadata' do
272
+ assert_equal 'Organization 1', exportable_template.to_export["organizations"].first
273
+ assert_equal 'Location 1', exportable_template.to_export["locations"].first
274
+ end
270
275
  end
271
276
 
272
277
  context 'there is existing template invocation of a job template' do
@@ -290,4 +295,20 @@ class JobTemplateTest < ActiveSupport::TestCase
290
295
  refute_valid job_template
291
296
  end
292
297
  end
298
+
299
+ context 'rendering' do
300
+ it 'renders nested template as a non-admin user' do
301
+ inner = FactoryBot.create(:job_template)
302
+ template_invocation = FactoryBot.create(:template_invocation)
303
+ template_invocation.template.template = "<wrap><%= render_template('#{inner.name}') %></wrap>"
304
+ template_invocation.template.save!
305
+
306
+ setup_user('view', 'job_templates')
307
+ renderer = InputTemplateRenderer.new template_invocation.template,
308
+ template_invocation.host,
309
+ template_invocation
310
+ result = renderer.render
311
+ result.must_equal "<wrap>#{inner.template}</wrap>"
312
+ end
313
+ end
293
314
  end
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.4
4
+ version: 1.5.5
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-07-12 00:00:00.000000000 Z
11
+ date: 2018-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deface
@@ -368,6 +368,7 @@ files:
368
368
  - test/functional/api/v2/template_inputs_controller_test.rb
369
369
  - test/functional/api/v2/template_invocations_controller_test.rb
370
370
  - test/functional/job_invocations_controller_test.rb
371
+ - test/functional/job_templates_controller_test.rb
371
372
  - test/test_plugin_helper.rb
372
373
  - test/unit/actions/run_host_job_test.rb
373
374
  - test/unit/actions/run_hosts_job_test.rb
@@ -416,7 +417,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
416
417
  version: '0'
417
418
  requirements: []
418
419
  rubyforge_project:
419
- rubygems_version: 2.6.12
420
+ rubygems_version: 2.7.3
420
421
  signing_key:
421
422
  specification_version: 4
422
423
  summary: A plugin bringing remote execution to the Foreman, completing the config
@@ -432,6 +433,7 @@ test_files:
432
433
  - test/functional/api/v2/template_inputs_controller_test.rb
433
434
  - test/functional/api/v2/template_invocations_controller_test.rb
434
435
  - test/functional/job_invocations_controller_test.rb
436
+ - test/functional/job_templates_controller_test.rb
435
437
  - test/test_plugin_helper.rb
436
438
  - test/unit/actions/run_host_job_test.rb
437
439
  - test/unit/actions/run_hosts_job_test.rb