foreman_remote_execution 3.2.2 → 3.3.0

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.
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