foreman_remote_execution 3.2.2 → 3.3.0

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 +4 -4
  2. data/.eslintrc +5 -30
  3. data/.gitignore +1 -0
  4. data/.hound.yml +0 -5
  5. data/.travis.yml +2 -3
  6. data/app/controllers/api/v2/job_invocations_controller.rb +1 -5
  7. data/app/controllers/api/v2/template_invocations_controller.rb +1 -4
  8. data/app/helpers/job_invocations_helper.rb +1 -1
  9. data/app/lib/actions/remote_execution/run_host_job.rb +3 -2
  10. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +1 -1
  11. data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +5 -7
  12. data/app/models/remote_execution_provider.rb +5 -0
  13. data/app/services/default_proxy_proxy_selector.rb +18 -0
  14. data/app/views/api/v2/job_invocations/main.json.rabl +2 -2
  15. data/db/seeds.d/70-job_templates.rb +1 -1
  16. data/lib/foreman_remote_execution/version.rb +1 -1
  17. data/package.json +16 -33
  18. data/test/functional/api/v2/job_invocations_controller_test.rb +14 -42
  19. data/test/models/orchestration/ssh_test.rb +0 -32
  20. data/test/unit/concerns/host_extensions_test.rb +0 -7
  21. data/webpack/index.js +1 -2
  22. data/webpack/react_app/components/jobInvocations/AggregateStatus/index.js +10 -0
  23. data/webpack/react_app/components/jobInvocations/AggregateStatus/index.test.js +6 -3
  24. data/webpack/react_app/components/jobInvocations/index.js +19 -7
  25. data/webpack/react_app/redux/actions/jobInvocations/index.js +12 -8
  26. data/webpack/react_app/redux/consts.js +1 -2
  27. data/webpack/react_app/redux/reducers/jobInvocations/index.fixtures.js +8 -40
  28. data/webpack/react_app/redux/reducers/jobInvocations/index.test.js +17 -11
  29. data/webpack/test_setup.js +2 -1
  30. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19c80592b90b1ba20ac213bae2ff36e9608202161e29718a063d3ac52ed249b3
4
- data.tar.gz: daa58fc0cfe1a9b788a49372c1d200579502331ef9e1f1424fa4a81270898a8b
3
+ metadata.gz: d00f4722f74258ad947432c2d8104522d3fb53749f7bac4e7c52914862af3ecc
4
+ data.tar.gz: 187645d51578339523b94fb83fe781cba865477a616c2d8c0980255be1fd0377
5
5
  SHA512:
6
- metadata.gz: c3b9ee1922a5ece712f8f431eb84de7ab5793d5108fe4959ce690a0acd6edb14f21ab5135cc1b7bcf37d3fbe81af45731fe1900e1803683c131bc3dba13aa931
7
- data.tar.gz: 5db356770b3017319ef2e4f0d1a16598025c9227b540661b2f3e6104f2bd522dbbfb351266eae0bc63d19667a2edc079de0136b50e648e6abddf973bbbe58ead
6
+ metadata.gz: 04c6a44e96bb03d75310d7dd48acd249503f04778c62f520d1101bf4719d195c2cd4d11e9a6ae63c97e24d8cbd50884dd090ec56a54aff933ee19f3379bbeec1
7
+ data.tar.gz: 165ba49b54b18ed4128c7b72a53ef52fea60f6f9a619ee7bd0a22545910cf68997b763e27f3569892b20dd528e70236c44b241c1d1b6c5fcec15a5a21f76af7e
data/.eslintrc CHANGED
@@ -1,32 +1,7 @@
1
1
  {
2
- "root": true,
3
- "extends": ["airbnb-base", "./node_modules/@theforeman/vendor-dev/eslint.extends.js"],
4
- "plugins": [
5
- "react"
6
- ],
7
- "env": {
8
- "browser": true,
9
- "es6": true,
10
- "node": true,
11
- "jasmine": true,
12
- "jest": true
13
- },
14
- "parser": "babel-eslint",
15
- "rules": {
16
- "react/jsx-uses-vars": "error",
17
- "react/jsx-uses-react": "error",
18
- "no-unused-vars": [
19
- "error",
20
- {
21
- "vars": "all",
22
- "args": "none"
23
- }
24
- ],
25
- "no-underscore-dangle": "off",
26
- "no-use-before-define": "off",
27
- "import/prefer-default-export": "off",
28
- // Import rules off for now due to HoundCI issue
29
- "import/no-unresolved": "off",
30
- "import/extensions": "off"
31
- }
2
+ "plugins": ["@theforeman/foreman"],
3
+ "extends": [
4
+ "plugin:@theforeman/foreman/core",
5
+ "plugin:@theforeman/foreman/plugins"
6
+ ]
32
7
  }
data/.gitignore CHANGED
@@ -14,3 +14,4 @@ locale/*/*.po.time_stamp
14
14
  Gemfile.lock
15
15
  node_modules/
16
16
  package-lock.json
17
+ coverage/
data/.hound.yml CHANGED
@@ -11,9 +11,4 @@ rubocop:
11
11
  jshint:
12
12
  enabled: false
13
13
 
14
- eslint:
15
- enabled: true
16
- config_file: .eslintrc
17
- ignore_file: .eslintignore
18
-
19
14
  fail_on_violations: true
data/.travis.yml CHANGED
@@ -1,6 +1,5 @@
1
1
  language: node_js
2
2
  node_js:
3
- - '6.10' # current EPEL 7
4
- - '6' # previous LTS
5
- - '8' # current LTS
3
+ - '10'
4
+ - '12'
6
5
  script: ./scripts/travis_run_js_tests.sh
@@ -19,10 +19,6 @@ module Api
19
19
  api :GET, '/job_invocations/:id', N_('Show job invocation')
20
20
  param :id, :identifier, :required => true
21
21
  def show
22
- @hosts = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
23
- @template_invocations = @job_invocation.template_invocations
24
- .where(host: @hosts)
25
- .includes(:input_values)
26
22
  end
27
23
 
28
24
  def_param_group :job_invocation do
@@ -150,7 +146,7 @@ module Api
150
146
  end
151
147
 
152
148
  def find_host
153
- @host = @nested_obj.targeting.hosts.authorized(:view_hosts, Host).find(params['host_id'])
149
+ @host = Host.authorized(:view_hosts).find(params['host_id'])
154
150
  rescue ActiveRecord::RecordNotFound
155
151
  not_found({ :error => { :message => (_("Host with id '%{id}' was not found") % { :id => params['host_id'] }) } })
156
152
  end
@@ -26,10 +26,7 @@ module Api
26
26
  private
27
27
 
28
28
  def resource_scope_for_template_invocations
29
- @job_invocation.template_invocations
30
- .includes(:host)
31
- .where(host: Host.authorized(:view_hosts, Host))
32
- .search_for(*search_options)
29
+ @job_invocation.template_invocations.search_for(*search_options)
33
30
  end
34
31
 
35
32
  def find_job_invocation
@@ -29,7 +29,7 @@ module JobInvocationsHelper
29
29
  end
30
30
 
31
31
  def preview_hosts(template_invocation)
32
- hosts = template_invocation.targeting.hosts.authorized(:view_hosts, Host).take(20)
32
+ hosts = template_invocation.targeting.hosts.take(20)
33
33
  hosts.map do |host|
34
34
  collapsed_preview(host) +
35
35
  render(:partial => 'job_invocations/user_input',
@@ -29,6 +29,9 @@ module Actions
29
29
 
30
30
  raise _('Could not use any template used in the job invocation') if template_invocation.blank?
31
31
 
32
+ provider = template_invocation.template.provider
33
+ proxy_selector = provider.required_proxy_selector_for(template_invocation.template) || proxy_selector
34
+
32
35
  provider_type = template_invocation.template.provider_type.to_s
33
36
  proxy = determine_proxy!(proxy_selector, provider_type, host)
34
37
 
@@ -36,8 +39,6 @@ module Actions
36
39
  script = renderer.render
37
40
  raise _('Failed rendering template: %s') % renderer.error_message unless script
38
41
 
39
- provider = template_invocation.template.provider
40
-
41
42
  additional_options = { :hostname => provider.find_ip_or_hostname(host),
42
43
  :script => script,
43
44
  :execution_timeout_interval => job_invocation.execution_timeout_interval,
@@ -49,7 +49,7 @@ module ForemanRemoteExecution
49
49
  keys = remote_execution_ssh_keys
50
50
  source = 'global'
51
51
  if keys.present?
52
- value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| [v].flatten.compact }
52
+ value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| v || [] }
53
53
  params['remote_execution_ssh_keys'] = {:value => value + keys, :safe_value => safe_value + keys, :source => source}
54
54
  end
55
55
  [:remote_execution_ssh_user, :remote_execution_effective_user_method,
@@ -8,10 +8,8 @@ module ForemanRemoteExecution
8
8
  register_rebuild(:queue_ssh_destroy, N_("SSH_#{self.to_s.split('::').first}"))
9
9
  end
10
10
 
11
- def drop_from_known_hosts(proxy_id)
12
- _, _, target = host_kind_target
13
- return true if target.nil?
14
-
11
+ def drop_from_known_hosts(args)
12
+ proxy_id, target = args
15
13
  proxy = ::SmartProxy.find(proxy_id)
16
14
  begin
17
15
  proxy.drop_host_from_known_hosts(target)
@@ -28,11 +26,11 @@ module ForemanRemoteExecution
28
26
  def ssh_destroy
29
27
  logger.debug "Scheduling SSH known_hosts cleanup"
30
28
 
31
- host, _kind, _target = host_kind_target
29
+ host, _kind, target = host_kind_target
32
30
  proxies = host.remote_execution_proxies('SSH').values
33
31
  proxies.flatten.uniq.each do |proxy|
34
32
  queue.create(id: queue_id(proxy.id), name: _("Remove SSH known hosts for %s") % self,
35
- priority: 200, action: [self, :drop_from_known_hosts, proxy.id])
33
+ priority: 200, action: [self, :drop_from_known_hosts, [proxy.id, target]])
36
34
  end
37
35
  end
38
36
 
@@ -42,7 +40,7 @@ module ForemanRemoteExecution
42
40
 
43
41
  def should_drop_from_known_hosts?
44
42
  host, = host_kind_target
45
- host && !host.new_record? && host.build && host.changes.key?('build')
43
+ host&.build && host&.changes&.key?('build')
46
44
  end
47
45
 
48
46
  private
@@ -98,5 +98,10 @@ class RemoteExecutionProvider
98
98
  def proxy_action_class
99
99
  ForemanRemoteExecutionCore::Actions::RunScript
100
100
  end
101
+
102
+ # Return a specific proxy selector to use for running a given template
103
+ # Returns either nil to use the default selector or an instance of a (sub)class of ::ForemanTasks::ProxySelector
104
+ def required_proxy_selector_for(_template)
105
+ end
101
106
  end
102
107
  end
@@ -0,0 +1,18 @@
1
+ class DefaultProxyProxySelector < ::RemoteExecutionProxySelector
2
+ def initialize
3
+ # TODO: Remove this once we have a reliable way of determining the internal proxy without katello
4
+ # Tracked as https://projects.theforeman.org/issues/29840
5
+ raise _('Internal proxy selector can only be used if Katello is enabled') unless defined?(::Katello)
6
+
7
+ super
8
+ end
9
+
10
+ def available_proxies(host, provider)
11
+ # TODO: Once we have a internal proxy marker/feature on the proxy, we can
12
+ # swap the implementation
13
+ internal_proxy = ::Katello.default_capsule
14
+ super.reduce({}) do |acc, (key, proxies)|
15
+ acc.merge(key => proxies.select { |proxy| proxy == internal_proxy })
16
+ end
17
+ end
18
+ end
@@ -19,7 +19,7 @@ child :targeting do
19
19
  attributes :bookmark_id, :search_query, :targeting_type, :user_id, :status, :status_label,
20
20
  :randomized_ordering
21
21
 
22
- child @hosts do
22
+ child :hosts do
23
23
  extends 'api/v2/hosts/base'
24
24
  end
25
25
  end
@@ -28,7 +28,7 @@ child :task do
28
28
  attributes :id, :state
29
29
  end
30
30
 
31
- child @template_invocations do
31
+ child :template_invocations do
32
32
  attributes :template_id, :template_name
33
33
  child :input_values do
34
34
  attributes :template_input_name, :template_input_id
@@ -4,7 +4,7 @@ User.as_anonymous_admin do
4
4
  JobTemplate.without_auditing do
5
5
  Dir[File.join("#{ForemanRemoteExecution::Engine.root}/app/views/templates/**/*.erb")].each do |template|
6
6
  sync = !Rails.env.test? && Setting[:remote_execution_sync_templates]
7
- template = JobTemplate.import_raw!(File.read(template), :default => true, :locked => true, :update => sync)
7
+ template = JobTemplate.import_raw!(File.read(template), :default => true, :lock => true, :update => sync)
8
8
  template.organizations = organizations if template.present?
9
9
  template.locations = locations if template.present?
10
10
  end
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '3.2.2'.freeze
2
+ VERSION = '3.3.0'.freeze
3
3
  end
data/package.json CHANGED
@@ -3,25 +3,14 @@
3
3
  "version": "1.0.0",
4
4
  "license": "GPL-3.0",
5
5
  "scripts": {
6
- "lint": "./node_modules/.bin/eslint -c .eslintrc webpack/ script/",
7
- "test": "node node_modules/.bin/jest webpack",
8
- "test:watch": "node node_modules/.bin/jest webpack --watchAll",
9
- "test:current": "node node_modules/.bin/jest webpack --watch"
10
- },
11
- "jest": {
12
- "verbose": true,
13
- "moduleDirectories": [
14
- "node_modules",
15
- "webpack"
16
- ],
17
- "setupFiles": [
18
- "raf/polyfill",
19
- "./webpack/test_setup.js"
20
- ],
21
- "testPathIgnorePatterns": [
22
- "/node_modules/",
23
- "<rootDir>/foreman/"
24
- ]
6
+ "lint": "tfm-lint --plugin -d /webpack",
7
+ "test": "tfm-test --plugin",
8
+ "test:watch": "tfm-test --plugin --watchAll",
9
+ "test:current": "tfm-test --plugin --watch",
10
+ "publish-coverage": "tfm-publish-coverage",
11
+ "stories": "tfm-stories --plugin",
12
+ "stories:build": "tfm-build-stories --plugin",
13
+ "stories:deploy": "surge --project .storybook-dist"
25
14
  },
26
15
  "repository": {
27
16
  "type": "git",
@@ -32,22 +21,16 @@
32
21
  },
33
22
  "devDependencies": {
34
23
  "@babel/core": "^7.7.0",
35
- "@theforeman/builder": "^4.0.2",
36
- "@theforeman/vendor-dev": "^4.0.2",
24
+ "@theforeman/builder": "^4.2.1",
25
+ "@theforeman/eslint-plugin-foreman": "^4.2.1",
26
+ "@theforeman/stories": "^4.2.1",
27
+ "@theforeman/test": "^4.2.1",
28
+ "@theforeman/vendor-dev": "^4.2.1",
37
29
  "babel-eslint": "^10.0.0",
38
- "babel-jest": "^24.9.0",
39
- "enzyme": "^3.2.0",
40
- "enzyme-adapter-react-16": "^1.1.0",
41
- "enzyme-to-json": "^3.1.2",
42
- "eslint": "^4.10.0",
43
- "eslint-config-airbnb": "^16.0.0",
44
- "eslint-plugin-import": "^2.8.0",
45
- "eslint-plugin-jest": "^21.2.0",
46
- "eslint-plugin-jsx-a11y": "^6.0.2",
47
- "eslint-plugin-react": "^7.4.0",
48
- "jest": "^24.9.0"
30
+ "eslint": "^6.8.0",
31
+ "prettier": "^1.19.1"
49
32
  },
50
33
  "peerDependencies": {
51
- "@theforeman/vendor": ">= 4.0.2"
34
+ "@theforeman/vendor": ">= 4.2.1"
52
35
  }
53
36
  }
@@ -4,7 +4,7 @@ module Api
4
4
  module V2
5
5
  class JobInvocationsControllerTest < ActionController::TestCase
6
6
  setup do
7
- @invocation = FactoryBot.create(:job_invocation, :with_template, :with_task, :with_unplanned_host)
7
+ @invocation = FactoryBot.create(:job_invocation, :with_template, :with_task)
8
8
  @template = FactoryBot.create(:job_template, :with_input)
9
9
 
10
10
  # Without this the template in template_invocations and in pattern_template_invocations
@@ -20,32 +20,18 @@ module Api
20
20
  assert_response :success
21
21
  end
22
22
 
23
- describe 'show' do
24
- test 'should get invocation detail' do
25
- get :show, params: { :id => @invocation.id }
26
- assert_response :success
27
- template = ActiveSupport::JSON.decode(@response.body)
28
- assert_not_empty template
29
- assert_equal template['job_category'], @invocation.job_category
30
- assert_not_empty template['targeting']['hosts']
31
- end
32
-
33
- test 'should get invocation detail when taxonomies are set' do
34
- taxonomy_params = %w(organization location).reduce({}) { |acc, cur| acc.merge("#{cur}_id" => FactoryBot.create(cur)) }
35
- get :show, params: taxonomy_params.merge(:id => @invocation.id)
36
- assert_response :success
37
- end
38
-
39
- test 'should see only permitted hosts' do
40
- @user = FactoryBot.create(:user, admin: false)
41
- setup_user('view', 'job_invocations', nil, @user)
42
- setup_user('view', 'hosts', 'name ~ nope.example.com', @user)
23
+ test 'should get invocation detail' do
24
+ get :show, params: { :id => @invocation.id }
25
+ assert_response :success
26
+ template = ActiveSupport::JSON.decode(@response.body)
27
+ assert_not_empty template
28
+ assert_equal template['job_category'], @invocation.job_category
29
+ end
43
30
 
44
- get :show, params: { :id => @invocation.id }, session: set_session_user(@user)
45
- assert_response :success
46
- response = ActiveSupport::JSON.decode(@response.body)
47
- assert_empty response['targeting']['hosts']
48
- end
31
+ test 'should get invocation detail when taxonomies are set' do
32
+ taxonomy_params = %w(organization location).reduce({}) { |acc, cur| acc.merge("#{cur}_id" => FactoryBot.create(cur)) }
33
+ get :show, params: taxonomy_params.merge(:id => @invocation.id)
34
+ assert_response :success
49
35
  end
50
36
 
51
37
  context 'creation' do
@@ -122,7 +108,7 @@ module Api
122
108
  end
123
109
 
124
110
  describe '#output' do
125
- let(:host) { @invocation.targeting.hosts.first }
111
+ let(:host) { @invocation.template_invocations_hosts.first }
126
112
 
127
113
  test 'should provide output for delayed task' do
128
114
  ForemanTasks::Task.any_instance.expects(:scheduled?).returns(true)
@@ -151,12 +137,6 @@ module Api
151
137
  assert_equal result['message'], "Job invocation not found by id '#{invocation_id}'"
152
138
  assert_response :missing
153
139
  end
154
-
155
- test 'should get output only for host in job invocation' do
156
- get :output, params: { job_invocation_id: @invocation.id,
157
- host_id: FactoryBot.create(:host).id }
158
- assert_response :missing
159
- end
160
140
  end
161
141
 
162
142
  describe 'raw output' do
@@ -168,7 +148,7 @@ module Api
168
148
  let(:fake_task) do
169
149
  OpenStruct.new :pending? => false, :main_action => OpenStruct.new(:live_output => fake_output)
170
150
  end
171
- let(:host) { @invocation.targeting.hosts.first }
151
+ let(:host) { @invocation.template_invocations_hosts.first }
172
152
 
173
153
  test 'should provide raw output for a host' do
174
154
  JobInvocation.any_instance.expects(:task).returns(OpenStruct.new(:scheduled? => false))
@@ -204,12 +184,6 @@ module Api
204
184
  assert_nil result['output']
205
185
  assert_response :success
206
186
  end
207
-
208
- test 'should get raw output only for host in job invocation' do
209
- get :raw_output, params: { job_invocation_id: @invocation.id,
210
- host_id: FactoryBot.create(:host).id }
211
- assert_response :missing
212
- end
213
187
  end
214
188
 
215
189
  test 'should cancel a job' do
@@ -258,13 +232,11 @@ module Api
258
232
  end
259
233
 
260
234
  test 'should not raise an exception when reruning failed has no hosts' do
261
- @invocation.targeting.hosts.first.destroy
262
235
  JobInvocation.any_instance.expects(:generate_description)
263
236
  JobInvocationComposer.any_instance
264
237
  .expects(:validate_job_category)
265
238
  .with(@invocation.job_category)
266
239
  .returns(@invocation.job_category)
267
-
268
240
  post :rerun, params: { :id => @invocation.id, :failed_only => true }
269
241
  assert_response :success
270
242
  result = ActiveSupport::JSON.decode(@response.body)
@@ -15,42 +15,10 @@ class SSHOrchestrationTest < ActiveSupport::TestCase
15
15
  end
16
16
 
17
17
  it 'attempts to drop IP address and hostname from smart proxies on rebuild' do
18
- host.stubs(:skip_orchestration?).returns false
19
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(interface.ip)
20
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(host.name)
21
-
22
18
  host.build = true
23
19
  host.save!
24
-
25
20
  ids = ["ssh_remove_known_hosts_interface_#{interface.ip}_#{proxy.id}",
26
21
  "ssh_remove_known_hosts_host_#{host.name}_#{proxy.id}"]
27
22
  _(host.queue.task_ids).must_equal ids
28
- _(host.queue.items.map(&:status)).must_equal %w(completed completed)
29
- end
30
-
31
- it 'does not fail on 404 from the smart proxy' do
32
- host.stubs(:skip_orchestration?).returns false
33
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).raises(RestClient::ResourceNotFound).twice
34
- host.build = true
35
- host.save!
36
- ids = ["ssh_remove_known_hosts_interface_#{interface.ip}_#{proxy.id}",
37
- "ssh_remove_known_hosts_host_#{host.name}_#{proxy.id}"]
38
- _(host.queue.task_ids).must_equal ids
39
- _(host.queue.items.map(&:status)).must_equal %w(completed completed)
40
- end
41
-
42
- it 'does not trigger the removal when creating a new host' do
43
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).never
44
- host = Host::Managed.new(:name => 'test', :ip => '127.0.0.1')
45
- host.stubs(:skip_orchestration?).returns false
46
- _(host.queue.task_ids).must_equal []
47
- end
48
-
49
- it 'does not call to the proxy when target is nil' do
50
- host.stubs(:skip_orchestration?).returns false
51
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(host.name)
52
- host.interfaces.first.stubs(:ip)
53
- host.destroy
54
- _(host.queue.items.map(&:status)).must_equal %w(completed completed)
55
23
  end
56
24
  end
@@ -48,13 +48,6 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
48
48
  _(host.host_param('remote_execution_ssh_keys')).must_include sshkey
49
49
  end
50
50
 
51
- it 'merges ssh key as a string from host parameters and proxies' do
52
- key = 'ssh-rsa not-even-a-key something@somewhere.com'
53
- host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_keys', :value => key)
54
- _(host.host_param('remote_execution_ssh_keys')).must_include key
55
- _(host.host_param('remote_execution_ssh_keys')).must_include sshkey
56
- end
57
-
58
51
  it 'has ssh keys in the parameters even when no user specified' do
59
52
  # this is a case, when using the helper in provisioning templates
60
53
  FactoryBot.create(:smart_proxy, :ssh)
data/webpack/index.js CHANGED
@@ -1,6 +1,5 @@
1
- import URI from 'urijs';
2
1
  // eslint-disable-next-line import/no-extraneous-dependencies
3
- import { mount, registerReducer } from 'foremanReact/common/MountingService';
2
+ import { registerReducer } from 'foremanReact/common/MountingService';
4
3
  // eslint-disable-next-line import/no-extraneous-dependencies
5
4
  import componentRegistry from 'foremanReact/components/componentRegistry';
6
5
  import JobInvocationContainer from './react_app/components/jobInvocations';
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import PropTypes from 'prop-types';
2
3
 
3
4
  const AggregateStatus = ({ statuses }) => (
4
5
  <div id="aggregate_statuses">
@@ -31,4 +32,13 @@ const AggregateStatus = ({ statuses }) => (
31
32
  </div>
32
33
  );
33
34
 
35
+ AggregateStatus.propTypes = {
36
+ statuses: PropTypes.shape({
37
+ cancelled: PropTypes.number,
38
+ failed: PropTypes.number,
39
+ pending: PropTypes.number,
40
+ success: PropTypes.number,
41
+ }).isRequired,
42
+ };
43
+
34
44
  export default AggregateStatus;
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
- import { shallow } from 'enzyme';
3
- import AggregateStatus from './index.js';
2
+ import { shallow } from '@theforeman/test';
3
+ import AggregateStatus from './index';
4
4
 
5
5
  jest.unmock('./index.js');
6
6
 
@@ -20,7 +20,10 @@ describe('AggregateStatus', () => {
20
20
 
21
21
  it('renders cards with props passed', () => {
22
22
  const statuses = {
23
- success: 19, failed: 20, cancelled: 31, pending: 3,
23
+ success: 19,
24
+ failed: 20,
25
+ cancelled: 31,
26
+ pending: 3,
24
27
  };
25
28
  const chartNumbers = shallow(<AggregateStatus statuses={statuses} />);
26
29
  const success = chartNumbers.find('#success_count').text();
@@ -4,14 +4,18 @@ import Immutable from 'seamless-immutable';
4
4
  import PropTypes from 'prop-types';
5
5
  // eslint-disable-next-line import/no-extraneous-dependencies
6
6
  import DonutChart from 'foremanReact/components/common/charts/DonutChart';
7
- import AggregateStatus from './AggregateStatus/index.js';
7
+ import AggregateStatus from './AggregateStatus';
8
8
  import * as JobInvocationActions from '../../redux/actions/jobInvocations';
9
9
 
10
- const colIndexOfMaxValue = columns => columns.reduce((iMax, x, i, arr) => (x[1] > arr[iMax][1] ? i : iMax), 0);
10
+ const colIndexOfMaxValue = columns =>
11
+ columns.reduce((iMax, x, i, arr) => (x[1] > arr[iMax][1] ? i : iMax), 0);
11
12
 
12
13
  class JobInvocationContainer extends React.Component {
13
14
  componentDidMount() {
14
- const { startJobInvocationsPolling, data: { url } } = this.props;
15
+ const {
16
+ startJobInvocationsPolling,
17
+ data: { url },
18
+ } = this.props;
15
19
 
16
20
  startJobInvocationsPolling(url);
17
21
  }
@@ -22,15 +26,20 @@ class JobInvocationContainer extends React.Component {
22
26
 
23
27
  return (
24
28
  <div id="job_invocations_chart_container">
25
- <DonutChart data={Immutable.asMutable(jobInvocations)}
26
- title={{type: 'percent', secondary: (jobInvocations[iMax] || [])[0]}}/>
29
+ <DonutChart
30
+ data={Immutable.asMutable(jobInvocations)}
31
+ title={{
32
+ type: 'percent',
33
+ secondary: (jobInvocations[iMax] || [])[0],
34
+ }}
35
+ />
27
36
  <AggregateStatus statuses={statuses} />
28
37
  </div>
29
38
  );
30
39
  }
31
40
  }
32
41
 
33
- const mapStateToProps = (state) => {
42
+ const mapStateToProps = state => {
34
43
  const {
35
44
  jobInvocations,
36
45
  statuses,
@@ -62,4 +71,7 @@ JobInvocationContainer.defaultProps = {
62
71
  jobInvocations: [['property', 3, 'color']],
63
72
  statuses: {},
64
73
  };
65
- export default connect(mapStateToProps, JobInvocationActions)(JobInvocationContainer);
74
+ export default connect(
75
+ mapStateToProps,
76
+ JobInvocationActions
77
+ )(JobInvocationContainer);
@@ -8,10 +8,10 @@ import {
8
8
  } from '../../consts';
9
9
 
10
10
  const defaultJobInvocationsPollingInterval = 1000;
11
- const jobInvocationsInterval = process.env.JOB_INVOCATIONS_POLLING ||
12
- defaultJobInvocationsPollingInterval;
11
+ const jobInvocationsInterval =
12
+ process.env.JOB_INVOCATIONS_POLLING || defaultJobInvocationsPollingInterval;
13
13
 
14
- const getJobInvocations = url => (dispatch, getState) => {
14
+ const getJobInvocations = url => async (dispatch, getState) => {
15
15
  function onGetJobInvocationsSuccess({ data }) {
16
16
  // If the job has finished, stop polling
17
17
  if (data.finished) {
@@ -41,7 +41,7 @@ const getJobInvocations = url => (dispatch, getState) => {
41
41
  if (jobInvocationsInterval) {
42
42
  setTimeout(
43
43
  () => dispatch(getJobInvocations(url)),
44
- jobInvocationsInterval,
44
+ jobInvocationsInterval
45
45
  );
46
46
  }
47
47
  }
@@ -52,10 +52,14 @@ const getJobInvocations = url => (dispatch, getState) => {
52
52
 
53
53
  if (getState().foremanRemoteExecutionReducers.jobInvocations.isPolling) {
54
54
  if (isDocumentVisible) {
55
- API.get(url)
56
- .then(onGetJobInvocationsSuccess)
57
- .catch(onGetJobInvocationsFailed)
58
- .then(triggerPolling);
55
+ try {
56
+ const data = await API.get(url);
57
+ onGetJobInvocationsSuccess(data);
58
+ } catch (error) {
59
+ onGetJobInvocationsFailed(error);
60
+ } finally {
61
+ triggerPolling();
62
+ }
59
63
  } else {
60
64
  // document is not visible, keep polling without api call
61
65
  triggerPolling();
@@ -2,5 +2,4 @@ export const JOB_INVOCATIONS_POLLING_STARTED =
2
2
  'JOB_INVOCATIONS_POLLING_STARTED';
3
3
  export const JOB_INVOCATIONS_GET_JOB_INVOCATIONS =
4
4
  'JOB_INVOCATIONS_GET_JOB_INVOCATIONS';
5
- export const JOB_INVOCATIONS_JOB_FINISHED =
6
- 'JOB_INVOCATIONS_JOB_FINISHED';
5
+ export const JOB_INVOCATIONS_JOB_FINISHED = 'JOB_INVOCATIONS_JOB_FINISHED';
@@ -15,26 +15,10 @@ export const pollingStarted = Immutable({
15
15
  export const jobInvocationsPayload = Immutable({
16
16
  jobInvocations: {
17
17
  job_invocations: [
18
- [
19
- 'Success',
20
- 100,
21
- '#B7312D',
22
- ],
23
- [
24
- 'Failed',
25
- 20,
26
- '#B7312D',
27
- ],
28
- [
29
- 'Pending',
30
- 40,
31
- '#B7312D',
32
- ],
33
- [
34
- 'Cancelled',
35
- 0,
36
- '#B7312D',
37
- ],
18
+ ['Success', 100, '#B7312D'],
19
+ ['Failed', 20, '#B7312D'],
20
+ ['Pending', 40, '#B7312D'],
21
+ ['Cancelled', 0, '#B7312D'],
38
22
  ],
39
23
  statuses: {
40
24
  cancelled: 0,
@@ -48,26 +32,10 @@ export const jobInvocationsPayload = Immutable({
48
32
  export const jobInvocationsReceived = Immutable({
49
33
  isPolling: true,
50
34
  jobInvocations: [
51
- [
52
- 'Success',
53
- 100,
54
- '#B7312D',
55
- ],
56
- [
57
- 'Failed',
58
- 20,
59
- '#B7312D',
60
- ],
61
- [
62
- 'Pending',
63
- 40,
64
- '#B7312D',
65
- ],
66
- [
67
- 'Cancelled',
68
- 0,
69
- '#B7312D',
70
- ],
35
+ ['Success', 100, '#B7312D'],
36
+ ['Failed', 20, '#B7312D'],
37
+ ['Pending', 40, '#B7312D'],
38
+ ['Cancelled', 0, '#B7312D'],
71
39
  ],
72
40
  statuses: {
73
41
  cancelled: 0,
@@ -18,20 +18,26 @@ describe('job invocations chart reducer', () => {
18
18
  expect(reducer(undefined, {})).toEqual(initialState);
19
19
  });
20
20
  it('should start polling given POLLING_STARTED', () => {
21
- expect(reducer(initialState, {
22
- type: JOB_INVOCATIONS_POLLING_STARTED,
23
- })).toEqual(pollingStarted);
21
+ expect(
22
+ reducer(initialState, {
23
+ type: JOB_INVOCATIONS_POLLING_STARTED,
24
+ })
25
+ ).toEqual(pollingStarted);
24
26
  });
25
27
  it('should stop polling given JOB_FINISHED', () => {
26
- expect(reducer(pollingStarted, {
27
- type: JOB_INVOCATIONS_JOB_FINISHED,
28
- payload: { jobInvocations: { job_invocations: [], statuses: {} } },
29
- })).toEqual(initialState);
28
+ expect(
29
+ reducer(pollingStarted, {
30
+ type: JOB_INVOCATIONS_JOB_FINISHED,
31
+ payload: { jobInvocations: { job_invocations: [], statuses: {} } },
32
+ })
33
+ ).toEqual(initialState);
30
34
  });
31
35
  it('should receive job invocations given GET_JOB_INVOCATIONS', () => {
32
- expect(reducer(pollingStarted, {
33
- type: JOB_INVOCATIONS_GET_JOB_INVOCATIONS,
34
- payload: jobInvocationsPayload,
35
- })).toEqual(jobInvocationsReceived);
36
+ expect(
37
+ reducer(pollingStarted, {
38
+ type: JOB_INVOCATIONS_GET_JOB_INVOCATIONS,
39
+ payload: jobInvocationsPayload,
40
+ })
41
+ ).toEqual(jobInvocationsReceived);
36
42
  });
37
43
  });
@@ -1,7 +1,8 @@
1
1
  import 'core-js/shim';
2
2
  import 'regenerator-runtime/runtime';
3
3
 
4
- import { configure } from 'enzyme';
4
+ import { configure } from '@theforeman/test';
5
+ // eslint-disable-next-line import/no-extraneous-dependencies
5
6
  import Adapter from 'enzyme-adapter-react-16';
6
7
 
7
8
  configure({ adapter: new Adapter() });
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: 3.2.2
4
+ version: 3.3.0
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: 2020-06-29 00:00:00.000000000 Z
11
+ date: 2020-06-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deface
@@ -205,6 +205,7 @@ files:
205
205
  - app/models/template_invocation_input_value.rb
206
206
  - app/overrides/execution_interface.rb
207
207
  - app/overrides/subnet_proxies.rb
208
+ - app/services/default_proxy_proxy_selector.rb
208
209
  - app/services/remote_execution_proxy_selector.rb
209
210
  - app/services/ui_notifications/remote_execution_jobs/base_job_finish.rb
210
211
  - app/views/api/v2/foreign_input_sets/base.json.rabl
@@ -426,7 +427,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
426
427
  - !ruby/object:Gem::Version
427
428
  version: '0'
428
429
  requirements: []
429
- rubygems_version: 3.0.3
430
+ rubygems_version: 3.0.4
430
431
  signing_key:
431
432
  specification_version: 4
432
433
  summary: A plugin bringing remote execution to the Foreman, completing the config