foreman_remote_execution 4.2.0 → 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/js_ci.yml +29 -0
  3. data/.github/workflows/{ci.yml → ruby_ci.yml} +22 -50
  4. data/.prettierrc +4 -0
  5. data/.rubocop.yml +13 -49
  6. data/.rubocop_todo.yml +326 -102
  7. data/Gemfile +1 -4
  8. data/app/controllers/api/v2/job_invocations_controller.rb +1 -1
  9. data/app/controllers/ui_job_wizard_controller.rb +18 -0
  10. data/app/lib/actions/remote_execution/run_host_job.rb +38 -1
  11. data/app/lib/foreman_remote_execution/renderer/scope/input.rb +1 -0
  12. data/app/models/foreign_input_set.rb +1 -1
  13. data/app/models/host_status/execution_status.rb +7 -0
  14. data/app/models/job_invocation.rb +1 -1
  15. data/app/models/job_invocation_composer.rb +1 -1
  16. data/app/views/template_invocations/show.html.erb +30 -23
  17. data/config/routes.rb +4 -0
  18. data/foreman_remote_execution.gemspec +1 -2
  19. data/lib/foreman_remote_execution/engine.rb +12 -1
  20. data/lib/foreman_remote_execution/version.rb +1 -1
  21. data/lib/tasks/foreman_remote_execution_tasks.rake +1 -18
  22. data/test/functional/api/v2/job_invocations_controller_test.rb +24 -4
  23. data/test/functional/api/v2/registration_controller_test.rb +4 -13
  24. data/test/functional/ui_job_wizard_controller_test.rb +16 -0
  25. data/webpack/JobWizard/JobWizard.js +32 -0
  26. data/webpack/JobWizard/index.js +32 -0
  27. data/webpack/Routes/routes.js +12 -0
  28. data/webpack/__mocks__/foremanReact/history.js +1 -0
  29. data/webpack/global_index.js +4 -0
  30. data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.js +1 -1
  31. data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.scss +0 -3
  32. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -1
  33. metadata +15 -19
data/Gemfile CHANGED
@@ -2,7 +2,4 @@ source 'http://rubygems.org'
2
2
 
3
3
  gemspec :name => 'foreman_remote_execution'
4
4
 
5
- gem 'rubocop', '~> 0.80.0'
6
- gem 'rubocop-minitest'
7
- gem 'rubocop-performance'
8
- gem 'rubocop-rails'
5
+ gem 'theforeman-rubocop', '~> 0.1.0.pre'
@@ -214,7 +214,7 @@ module Api
214
214
  end
215
215
 
216
216
  def host_output(job_invocation, host, default: nil, since: nil, raw: false)
217
- refresh = true
217
+ refresh = !job_invocation.finished?
218
218
 
219
219
  if (task = job_invocation.sub_task_for_host(host))
220
220
  refresh = task.pending?
@@ -0,0 +1,18 @@
1
+ class UiJobWizardController < ::Api::V2::BaseController
2
+ def categories
3
+ job_categories = resource_scope
4
+ .search_for("job_category ~ \"#{params[:search]}\"")
5
+ .select(:job_category).distinct
6
+ .reorder(:job_category)
7
+ .pluck(:job_category)
8
+ render :json => {:job_categories =>job_categories}
9
+ end
10
+
11
+ def resource_class
12
+ JobTemplate
13
+ end
14
+
15
+ def action_permission
16
+ :view_job_templates
17
+ end
18
+ end
@@ -3,6 +3,7 @@ module Actions
3
3
  class RunHostJob < Actions::EntryAction
4
4
  include ::Actions::Helpers::WithContinuousOutput
5
5
  include ::Actions::Helpers::WithDelegatedAction
6
+ include ::Actions::ObservableAction
6
7
 
7
8
  middleware.do_not_use Dynflow::Middleware::Common::Transaction
8
9
  middleware.use Actions::Middleware::HideSecrets
@@ -16,7 +17,7 @@ module Actions
16
17
  end
17
18
 
18
19
  def plan(job_invocation, host, template_invocation, proxy_selector = ::RemoteExecutionProxySelector.new, options = {})
19
- action_subject(host, :job_category => job_invocation.job_category, :description => job_invocation.description)
20
+ action_subject(host, :job_category => job_invocation.job_category, :description => job_invocation.description, :job_invocation_id => job_invocation.id)
20
21
 
21
22
  template_invocation.host_id = host.id
22
23
  template_invocation.run_host_job_task_id = task.id
@@ -121,6 +122,26 @@ module Actions
121
122
  delegated_output[:exit_status]
122
123
  end
123
124
 
125
+ def host_id
126
+ input['host']['id']
127
+ end
128
+
129
+ def host_name
130
+ input['host']['name']
131
+ end
132
+
133
+ def job_invocation_id
134
+ input['job_invocation_id']
135
+ end
136
+
137
+ def job_invocation
138
+ @job_invocation ||= ::JobInvocation.authorized.find(job_invocation_id)
139
+ end
140
+
141
+ def host
142
+ @host ||= ::Host.authorized.find(host_id)
143
+ end
144
+
124
145
  private
125
146
 
126
147
  def update_host_status
@@ -173,6 +194,22 @@ module Actions
173
194
  end
174
195
  proxy
175
196
  end
197
+
198
+ extend ApipieDSL::Class
199
+ apipie :class, "An action representing execution of a job against a host" do
200
+ name 'Actions::RemoteExecution::RunHostJob'
201
+ refs 'Actions::RemoteExecution::RunHostJob'
202
+ sections only: %w[all webhooks]
203
+ property :task, object_of: 'Task', desc: 'Returns the task to which this action belongs'
204
+ property :host_name, String, desc: "Returns the name of the host"
205
+ property :host_id, Integer, desc: "Returns the id of the host"
206
+ property :host, object_of: 'Host', desc: "Returns the host"
207
+ property :job_invocation_id, Integer, desc: "Returns the id of the job invocation"
208
+ property :job_invocation, object_of: 'JobInvocation', desc: "Returns the job invocation"
209
+ end
210
+ class Jail < ::Actions::ObservableAction::Jail
211
+ allow :host_name, :host_id, :host, :job_invocation_id, :job_invocation
212
+ end
176
213
  end
177
214
  end
178
215
  end
@@ -6,6 +6,7 @@ module ForemanRemoteExecution
6
6
  extend ApipieDSL::Class
7
7
 
8
8
  attr_reader :template, :host, :invocation, :input_template_instance, :current_user
9
+ attr_accessor :error_message
9
10
  delegate :input, to: :input_template_instance
10
11
 
11
12
  apipie :class, 'Macros related to template rendering' do
@@ -4,7 +4,7 @@ class ForeignInputSet < ApplicationRecord
4
4
  class CircularDependencyError < Foreman::Exception
5
5
  end
6
6
 
7
- attr_exportable :exclude, :include, :include_all, :template => ->(input_set) { input_set.template.name }
7
+ attr_exportable :exclude, :include, :include_all, :template => ->(input_set) { input_set.target_template.name }
8
8
 
9
9
  belongs_to :template
10
10
  belongs_to :target_template, :class_name => 'Template'
@@ -49,6 +49,13 @@ class HostStatus::ExecutionStatus < HostStatus::Status
49
49
  end
50
50
  end
51
51
 
52
+ def status_link
53
+ job_invocation = last_stopped_task.parent_task.job_invocations.first
54
+ return nil unless User.current.can?(:view_job_invocations, job_invocation)
55
+
56
+ Rails.application.routes.url_helpers.job_invocation_path(job_invocation)
57
+ end
58
+
52
59
  class ExecutionTaskStatusMapper
53
60
  attr_accessor :task
54
61
 
@@ -241,7 +241,7 @@ class JobInvocation < ApplicationRecord
241
241
  end
242
242
 
243
243
  def finished?
244
- !task.pending?
244
+ !(task.nil? || task.pending?)
245
245
  end
246
246
 
247
247
  def missing_hosts_count
@@ -482,7 +482,7 @@ class JobInvocationComposer
482
482
 
483
483
  def input_value_for(input)
484
484
  invocations = pattern_template_invocations
485
- default = TemplateInvocationInputValue.new
485
+ default = TemplateInvocationInputValue.new(:template_input_id => input.id)
486
486
  invocations.map(&:input_values).flatten.detect { |iv| iv.template_input_id == input.id } || default
487
487
  end
488
488
 
@@ -1,12 +1,16 @@
1
1
  <% items = [{ :caption => _('Job invocations'), :url => job_invocations_path },
2
2
  { :caption => @template_invocation.job_invocation.description,
3
- :url => job_invocation_path(@template_invocation.job_invocation_id) },
4
- { :caption => _('Template Invocation for %s') % @template_invocation.host.name }] %>
3
+ :url => job_invocation_path(@template_invocation.job_invocation_id) }]
5
4
 
6
- <% breadcrumbs(:resource_url => template_invocations_api_job_invocation_path(@template_invocation.job_invocation_id),
5
+ if @host
6
+ items << { :caption => _('Template Invocation for %s') % @host.name }
7
+ breadcrumbs(:resource_url => template_invocations_api_job_invocation_path(@template_invocation.job_invocation_id),
7
8
  :name_field => 'host_name',
8
9
  :switcher_item_url => template_invocation_path(':id'),
9
10
  :items => items)
11
+ else
12
+ breadcrumbs(items: items, switchable: false)
13
+ end
10
14
  %>
11
15
 
12
16
  <% stylesheet 'foreman_remote_execution/foreman_remote_execution' %>
@@ -18,31 +22,34 @@
18
22
  <%= button_group(link_to_function(_('Toggle command'), '$("div.preview").toggle()', :class => 'btn btn-default'),
19
23
  link_to_function(_('Toggle STDERR'), '$("div.line.stderr").toggle()', :class => 'btn btn-default'),
20
24
  link_to_function(_('Toggle STDOUT'), '$("div.line.stdout").toggle()', :class => 'btn btn-default'),
21
- link_to_function(_('Toggle DEBUG'), '$("div.line.debug").toggle()', :class => 'btn btn-default')) %>
25
+ link_to_function(_('Toggle DEBUG'), '$("div.line.debug").toggle()', :class => 'btn btn-default')) if @host %>
22
26
  <%= button_group(template_invocation_task_buttons(@template_invocation_task, @template_invocation.job_invocation)) %>
23
27
  </div>
24
28
  </div>
29
+ <% if @host %>
30
+ <h3><%= _('Target: ') %><%= link_to(@host.name, host_path(@host)) %></h3>
25
31
 
26
- <h3><%= _('Target: ') %><%= link_to(@host.name, host_path(@host)) %></h3>
27
-
28
- <div class="preview hidden">
29
- <%= preview_box(@template_invocation, @host) %>
30
- </div>
32
+ <div class="preview hidden">
33
+ <%= preview_box(@template_invocation, @host) %>
34
+ </div>
31
35
 
32
- <div class="terminal" data-refresh-url="<%= template_invocation_path(@template_invocation) %>">
33
- <% if @error %>
34
- <div class="line error"><%= @error %></div>
35
- <% else %>
36
- <%= link_to_function(_('Scroll to bottom'), '$("html, body").animate({ scrollTop: $(document).height() }, "slow");', :class => 'pull-right scroll-link-bottom') %>
36
+ <div class="terminal" data-refresh-url="<%= template_invocation_path(@template_invocation) %>">
37
+ <% if @error %>
38
+ <div class="line error"><%= @error %></div>
39
+ <% else %>
40
+ <%= link_to_function(_('Scroll to bottom'), '$("html, body").animate({ scrollTop: $(document).height() }, "slow");', :class => 'pull-right scroll-link-bottom') %>
37
41
 
38
- <div class="printable">
39
- <%= render :partial => 'output_line_set', :collection => normalize_line_sets(@line_sets) %>
40
- </div>
42
+ <div class="printable">
43
+ <%= render :partial => 'output_line_set', :collection => normalize_line_sets(@line_sets) %>
44
+ </div>
41
45
 
42
- <%= link_to_function(_('Scroll to top'), '$("html, body").animate({ scrollTop: 0 }, "slow");', :class => 'pull-right scroll-link-top') %>
43
- <% end %>
44
- </div>
46
+ <%= link_to_function(_('Scroll to top'), '$("html, body").animate({ scrollTop: 0 }, "slow");', :class => 'pull-right scroll-link-top') %>
47
+ <% end %>
48
+ </div>
45
49
 
46
- <script>
47
- <%= render :partial => 'refresh.js' %>
48
- </script>
50
+ <script>
51
+ <%= render :partial => 'refresh.js' %>
52
+ </script>
53
+ <% else %>
54
+ <%= _("Could not display data for job invocation.") %>
55
+ <% end %>
data/config/routes.rb CHANGED
@@ -43,6 +43,9 @@ Rails.application.routes.draw do
43
43
  get 'cockpit/host_ssh_params/:id', to: 'cockpit#host_ssh_params'
44
44
  end
45
45
  get 'cockpit/redirect', to: 'cockpit#redirect'
46
+ get 'ui_job_wizard/categories', to: 'ui_job_wizard#categories'
47
+
48
+ match '/experimental/job_wizard', to: 'react#index', :via => [:get]
46
49
 
47
50
  namespace :api, :defaults => {:format => 'json'} do
48
51
  scope '(:apiv)', :module => :v2, :defaults => {:apiv => 'v2'}, :apiv => /v1|v2/, :constraints => ApiConstraints.new(:version => 2, :default => true) do
@@ -56,6 +59,7 @@ Rails.application.routes.draw do
56
59
  post 'rerun'
57
60
  get 'template_invocations', :to => 'template_invocations#template_invocations'
58
61
  get 'outputs'
62
+ post 'outputs'
59
63
  end
60
64
  end
61
65
 
@@ -26,9 +26,8 @@ Gem::Specification.new do |s|
26
26
  s.add_dependency 'deface'
27
27
  s.add_dependency 'dynflow', '>= 1.0.2', '< 2.0.0'
28
28
  s.add_dependency 'foreman_remote_execution_core'
29
- s.add_dependency 'foreman-tasks', '>= 0.15.1'
29
+ s.add_dependency 'foreman-tasks', '>= 4.0.0'
30
30
 
31
31
  s.add_development_dependency 'factory_bot_rails', '~> 4.8.0'
32
- s.add_development_dependency 'rubocop', '~> 0.80.0'
33
32
  s.add_development_dependency 'rdoc'
34
33
  end
@@ -53,6 +53,7 @@ module ForemanRemoteExecution
53
53
 
54
54
  initializer 'foreman_remote_execution.register_plugin', before: :finisher_hook do |_app|
55
55
  Foreman::Plugin.register :foreman_remote_execution do
56
+ register_global_js_file 'global'
56
57
  requires_foreman '>= 2.2'
57
58
 
58
59
  apipie_documented_controllers ["#{ForemanRemoteExecution::Engine.root}/app/controllers/api/v2/*.rb"]
@@ -68,7 +69,8 @@ module ForemanRemoteExecution
68
69
  permission :view_job_templates, { :job_templates => [:index, :show, :revision, :auto_complete_search, :auto_complete_job_category, :preview, :export],
69
70
  :'api/v2/job_templates' => [:index, :show, :revision, :export],
70
71
  :'api/v2/template_inputs' => [:index, :show],
71
- :'api/v2/foreign_input_sets' => [:index, :show]}, :resource_type => 'JobTemplate'
72
+ :'api/v2/foreign_input_sets' => [:index, :show],
73
+ :ui_job_wizard => [:categories]}, :resource_type => 'JobTemplate'
72
74
  permission :create_job_templates, { :job_templates => [:new, :create, :clone_template, :import],
73
75
  :'api/v2/job_templates' => [:create, :clone, :import] }, :resource_type => 'JobTemplate'
74
76
  permission :edit_job_templates, { :job_templates => [:edit, :update],
@@ -137,6 +139,13 @@ module ForemanRemoteExecution
137
139
  parent: :monitor_menu,
138
140
  after: :audits
139
141
 
142
+ menu :labs_menu, :job_wizard,
143
+ url_hash: { controller: 'job_wizard', action: :index },
144
+ caption: N_('Job wizard'),
145
+ parent: :lab_features_menu,
146
+ url: 'experimental/job_wizard',
147
+ after: :host_wizard
148
+
140
149
  register_custom_status HostStatus::ExecutionStatus
141
150
  # add dashboard widget
142
151
  # widget 'foreman_remote_execution_widget', name: N_('Foreman plugin template widget'), sizex: 4, sizey: 1
@@ -157,6 +166,8 @@ module ForemanRemoteExecution
157
166
  extend_page 'registration/_form' do |cx|
158
167
  cx.add_pagelet :global_registration, name: N_('Remote Execution'), partial: 'api/v2/registration/form', priority: 100, id: 'remote_execution_interface'
159
168
  end
169
+ ForemanTasks.dynflow.eager_load_actions!
170
+ extend_observable_events(::Dynflow::Action.descendants.select { |klass| klass <= ::Actions::ObservableAction }.map(&:namespaced_event_names))
160
171
  end
161
172
  end
162
173
 
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '4.2.0'.freeze
2
+ VERSION = '4.3.1'.freeze
3
3
  end
@@ -20,26 +20,9 @@ namespace :test do
20
20
  end
21
21
  end
22
22
 
23
- namespace :foreman_remote_execution do
24
- task :rubocop do
25
- begin
26
- require 'rubocop/rake_task'
27
- RuboCop::RakeTask.new(:rubocop_foreman_remote_execution) do |task|
28
- task.patterns = ["#{ForemanRemoteExecution::Engine.root}/app/**/*.rb",
29
- "#{ForemanRemoteExecution::Engine.root}/lib/**/*.rb",
30
- "#{ForemanRemoteExecution::Engine.root}/test/**/*.rb"]
31
- end
32
- rescue
33
- puts 'Rubocop not loaded.'
34
- end
35
-
36
- Rake::Task['rubocop_foreman_remote_execution'].invoke
37
- end
38
- end
39
-
40
23
  Rake::Task[:test].enhance ['test:foreman_remote_execution']
41
24
 
42
25
  load 'tasks/jenkins.rake'
43
26
  if Rake::Task.task_defined?(:'jenkins:unit')
44
- Rake::Task['jenkins:unit'].enhance ['test:foreman_remote_execution', 'foreman_remote_execution:rubocop']
27
+ Rake::Task['jenkins:unit'].enhance ['test:foreman_remote_execution']
45
28
  end
@@ -137,6 +137,7 @@ module Api
137
137
 
138
138
  test 'should provide empty output for host which does not have a task yet' do
139
139
  JobInvocation.any_instance.expects(:sub_task_for_host).returns(nil)
140
+ JobInvocation.any_instance.expects(:finished?).returns(false)
140
141
  get :output, params: { :job_invocation_id => @invocation.id, :host_id => host.id }
141
142
  result = ActiveSupport::JSON.decode(@response.body)
142
143
  assert_equal result['refresh'], true
@@ -144,6 +145,15 @@ module Api
144
145
  assert_response :success
145
146
  end
146
147
 
148
+ test 'should provide empty output marked as done for host which does not have a task when the job is finished' do
149
+ JobInvocation.any_instance.expects(:sub_task_for_host).returns(nil)
150
+ get :output, params: { :job_invocation_id => @invocation.id, :host_id => host.id }
151
+ result = ActiveSupport::JSON.decode(@response.body)
152
+ assert_equal result['refresh'], false
153
+ assert_equal result['output'], []
154
+ assert_response :success
155
+ end
156
+
147
157
  test 'should fail with 404 for non-existing job invocation' do
148
158
  invocation_id = @invocation.id + 1
149
159
  assert_empty JobInvocation.where(:id => invocation_id)
@@ -166,7 +176,17 @@ module Api
166
176
  result = ActiveSupport::JSON.decode(@response.body)
167
177
  host_output = result['outputs'].first
168
178
  assert_equal host_output['host_id'], @invocation.targeting.host_ids.first
169
- assert_equal host_output['refresh'], true
179
+ assert_equal host_output['refresh'], false
180
+ assert_equal host_output['output'], []
181
+ assert_response :success
182
+ end
183
+
184
+ test 'should provide outputs for selected hosts in the job' do
185
+ post :outputs, params: { :id => @invocation.id, :search_query => "id = #{@invocation.targeting.host_ids.first}" }, as: :json
186
+ result = ActiveSupport::JSON.decode(@response.body)
187
+ host_output = result['outputs'].first
188
+ assert_equal host_output['host_id'], @invocation.targeting.host_ids.first
189
+ assert_equal host_output['refresh'], false
170
190
  assert_equal host_output['output'], []
171
191
  assert_response :success
172
192
  end
@@ -191,7 +211,7 @@ module Api
191
211
  let(:host) { @invocation.targeting.hosts.first }
192
212
 
193
213
  test 'should provide raw output for a host' do
194
- JobInvocation.any_instance.expects(:task).returns(OpenStruct.new(:scheduled? => false))
214
+ JobInvocation.any_instance.expects(:task).times(3).returns(OpenStruct.new(:scheduled? => false, :pending? => false))
195
215
  JobInvocation.any_instance.expects(:sub_task_for_host).returns(fake_task)
196
216
  get :raw_output, params: { :job_invocation_id => @invocation.id, :host_id => host.id }
197
217
  result = ActiveSupport::JSON.decode(@response.body)
@@ -204,7 +224,7 @@ module Api
204
224
  start_time = Time.now
205
225
  JobInvocation.any_instance
206
226
  .expects(:task).twice
207
- .returns(OpenStruct.new(:scheduled? => true, :start_at => start_time))
227
+ .returns(OpenStruct.new(:scheduled? => true, :start_at => start_time, :pending? => true))
208
228
  JobInvocation.any_instance.expects(:sub_task_for_host).never
209
229
  get :raw_output, params: { :job_invocation_id => @invocation.id, :host_id => host.id }
210
230
  result = ActiveSupport::JSON.decode(@response.body)
@@ -216,7 +236,7 @@ module Api
216
236
  end
217
237
 
218
238
  test 'should provide raw output for host without task' do
219
- JobInvocation.any_instance.expects(:task).returns(OpenStruct.new(:scheduled? => false))
239
+ JobInvocation.any_instance.expects(:task).times(3).returns(OpenStruct.new(:scheduled? => false, :pending? => true))
220
240
  JobInvocation.any_instance.expects(:sub_task_for_host)
221
241
  get :raw_output, params: { :job_invocation_id => @invocation.id, :host_id => host.id }
222
242
  result = ActiveSupport::JSON.decode(@response.body)
@@ -7,11 +7,10 @@ module Api
7
7
  describe 'host registration' do
8
8
  let(:organization) { FactoryBot.create(:organization) }
9
9
  let(:tax_location) { FactoryBot.create(:location) }
10
- let(:template_kind) { template_kinds(:registration) }
11
- let(:registration_template) do
10
+ let(:template) do
12
11
  FactoryBot.create(
13
12
  :provisioning_template,
14
- template_kind: template_kind,
13
+ template_kind: template_kinds(:host_init_config),
15
14
  template: 'template content <%= @host.name %>',
16
15
  locations: [tax_location],
17
16
  organizations: [organization]
@@ -23,7 +22,7 @@ module Api
23
22
  :with_associations,
24
23
  family: 'Redhat',
25
24
  provisioning_templates: [
26
- registration_template,
25
+ template,
27
26
  ]
28
27
  )
29
28
  end
@@ -36,17 +35,9 @@ module Api
36
35
  operatingsystem_id: os.id } }
37
36
  end
38
37
 
39
- setup do
40
- FactoryBot.create(
41
- :os_default_template,
42
- template_kind: template_kind,
43
- provisioning_template: registration_template,
44
- operatingsystem: os
45
- )
46
- end
47
-
48
38
  describe 'remote_execution_interface' do
49
39
  setup do
40
+ Setting[:default_host_init_config_template] = template.name
50
41
  @host = Host.create(host_params[:host])
51
42
  @interface0 = FactoryBot.create(:nic_managed, host: @host, identifier: 'dummy0', execution: false)
52
43
  end