foreman_remote_execution 4.2.3 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
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[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
@@ -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
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
@@ -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.3'.freeze
2
+ VERSION = '4.3.0'.freeze
3
3
  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,7 @@ 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
170
180
  assert_equal host_output['output'], []
171
181
  assert_response :success
172
182
  end
@@ -176,7 +186,7 @@ module Api
176
186
  result = ActiveSupport::JSON.decode(@response.body)
177
187
  host_output = result['outputs'].first
178
188
  assert_equal host_output['host_id'], @invocation.targeting.host_ids.first
179
- assert_equal host_output['refresh'], true
189
+ assert_equal host_output['refresh'], false
180
190
  assert_equal host_output['output'], []
181
191
  assert_response :success
182
192
  end
@@ -201,7 +211,7 @@ module Api
201
211
  let(:host) { @invocation.targeting.hosts.first }
202
212
 
203
213
  test 'should provide raw output for a host' do
204
- 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))
205
215
  JobInvocation.any_instance.expects(:sub_task_for_host).returns(fake_task)
206
216
  get :raw_output, params: { :job_invocation_id => @invocation.id, :host_id => host.id }
207
217
  result = ActiveSupport::JSON.decode(@response.body)
@@ -214,7 +224,7 @@ module Api
214
224
  start_time = Time.now
215
225
  JobInvocation.any_instance
216
226
  .expects(:task).twice
217
- .returns(OpenStruct.new(:scheduled? => true, :start_at => start_time))
227
+ .returns(OpenStruct.new(:scheduled? => true, :start_at => start_time, :pending? => true))
218
228
  JobInvocation.any_instance.expects(:sub_task_for_host).never
219
229
  get :raw_output, params: { :job_invocation_id => @invocation.id, :host_id => host.id }
220
230
  result = ActiveSupport::JSON.decode(@response.body)
@@ -226,7 +236,7 @@ module Api
226
236
  end
227
237
 
228
238
  test 'should provide raw output for host without task' do
229
- 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))
230
240
  JobInvocation.any_instance.expects(:sub_task_for_host)
231
241
  get :raw_output, params: { :job_invocation_id => @invocation.id, :host_id => host.id }
232
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
@@ -0,0 +1,16 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class UiJobWizardControllerTest < ActionController::TestCase
4
+ def setup
5
+ FactoryBot.create(:job_template, :job_category => 'cat1')
6
+ FactoryBot.create(:job_template, :job_category => 'cat2')
7
+ FactoryBot.create(:job_template, :job_category => 'cat2')
8
+ end
9
+
10
+ test 'should respond with categories' do
11
+ get :categories, :params => {}, :session => set_session_user
12
+ assert_response :success
13
+ res = JSON.parse @response.body
14
+ assert_equal ['cat1','cat2'], res['job_categories']
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { Wizard } from '@patternfly/react-core';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import history from 'foremanReact/history';
5
+
6
+ export const JobWizard = () => {
7
+ const steps = [
8
+ {
9
+ name: __('Category and template'),
10
+ component: <p>Category and template</p>,
11
+ },
12
+ { name: __('Target hosts'), component: <p>TargetHosts </p> },
13
+ { name: __('Advanced fields'), component: <p> AdvancedFields </p> },
14
+ { name: __('Schedule'), component: <p>Schedule</p> },
15
+ {
16
+ name: __('Review details'),
17
+ component: <p>ReviewDetails</p>,
18
+ nextButtonText: 'Run',
19
+ },
20
+ ];
21
+ const title = __('Run Job');
22
+ return (
23
+ <Wizard
24
+ onClose={() => history.goBack()}
25
+ navAriaLabel={`${title} steps`}
26
+ steps={steps}
27
+ height="70vh"
28
+ />
29
+ );
30
+ };
31
+
32
+ export default JobWizard;
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { Title, Divider } from '@patternfly/react-core';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
5
+ import { JobWizard } from './JobWizard';
6
+
7
+ const JobWizardPage = () => {
8
+ const title = __('Run job');
9
+ const breadcrumbOptions = {
10
+ breadcrumbItems: [
11
+ { caption: __('Jobs'), url: `/jobs` },
12
+ { caption: title },
13
+ ],
14
+ };
15
+ return (
16
+ <PageLayout
17
+ header={title}
18
+ breadcrumbOptions={breadcrumbOptions}
19
+ searchable={false}
20
+ >
21
+ <React.Fragment>
22
+ <Title headingLevel="h2" size="2xl">
23
+ {title}
24
+ </Title>
25
+ <Divider component="div" />
26
+ <JobWizard />
27
+ </React.Fragment>
28
+ </PageLayout>
29
+ );
30
+ };
31
+
32
+ export default JobWizardPage;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import JobWizardPage from '../JobWizard';
3
+
4
+ const ForemanREXRoutes = [
5
+ {
6
+ path: '/experimental/job_wizard',
7
+ exact: true,
8
+ render: props => <JobWizardPage {...props} />,
9
+ },
10
+ ];
11
+
12
+ export default ForemanREXRoutes;
@@ -0,0 +1 @@
1
+ export default { goBack: () => null };
@@ -0,0 +1,4 @@
1
+ import { registerRoutes } from 'foremanReact/routes/RoutingService';
2
+ import routes from './Routes/routes';
3
+
4
+ registerRoutes('foreman_remote_execution', routes);
@@ -39,7 +39,7 @@ const TargetingHostsPage = ({
39
39
  <br />
40
40
  <TargetingHosts apiStatus={apiStatus} items={items} />
41
41
  <Pagination
42
- viewType="list"
42
+ viewType="table"
43
43
  itemCount={totalHosts}
44
44
  pagination={pagination}
45
45
  onChange={args => handlePagination(args)}
@@ -1,6 +1,3 @@
1
- .targeting-hosts-pagination {
2
- margin-top: -7px;
3
- }
4
1
  #targeting_hosts {
5
2
  min-height: 350px;
6
3
  }
@@ -62,7 +62,7 @@ exports[`TargetingHostsPage renders 1`] = `
62
62
  "perPage": 20,
63
63
  }
64
64
  }
65
- viewType="list"
65
+ viewType="table"
66
66
  />
67
67
  </div>
68
68
  `;