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.
- checksums.yaml +4 -4
- data/.eslintrc +5 -30
- data/.gitignore +1 -0
- data/.hound.yml +0 -5
- data/.travis.yml +2 -3
- data/app/controllers/api/v2/job_invocations_controller.rb +1 -5
- data/app/controllers/api/v2/template_invocations_controller.rb +1 -4
- data/app/helpers/job_invocations_helper.rb +1 -1
- data/app/lib/actions/remote_execution/run_host_job.rb +3 -2
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +1 -1
- data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +5 -7
- data/app/models/remote_execution_provider.rb +5 -0
- data/app/services/default_proxy_proxy_selector.rb +18 -0
- data/app/views/api/v2/job_invocations/main.json.rabl +2 -2
- data/db/seeds.d/70-job_templates.rb +1 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/package.json +16 -33
- data/test/functional/api/v2/job_invocations_controller_test.rb +14 -42
- data/test/models/orchestration/ssh_test.rb +0 -32
- data/test/unit/concerns/host_extensions_test.rb +0 -7
- data/webpack/index.js +1 -2
- data/webpack/react_app/components/jobInvocations/AggregateStatus/index.js +10 -0
- data/webpack/react_app/components/jobInvocations/AggregateStatus/index.test.js +6 -3
- data/webpack/react_app/components/jobInvocations/index.js +19 -7
- data/webpack/react_app/redux/actions/jobInvocations/index.js +12 -8
- data/webpack/react_app/redux/consts.js +1 -2
- data/webpack/react_app/redux/reducers/jobInvocations/index.fixtures.js +8 -40
- data/webpack/react_app/redux/reducers/jobInvocations/index.test.js +17 -11
- data/webpack/test_setup.js +2 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d00f4722f74258ad947432c2d8104522d3fb53749f7bac4e7c52914862af3ecc
|
4
|
+
data.tar.gz: 187645d51578339523b94fb83fe781cba865477a616c2d8c0980255be1fd0377
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04c6a44e96bb03d75310d7dd48acd249503f04778c62f520d1101bf4719d195c2cd4d11e9a6ae63c97e24d8cbd50884dd090ec56a54aff933ee19f3379bbeec1
|
7
|
+
data.tar.gz: 165ba49b54b18ed4128c7b72a53ef52fea60f6f9a619ee7bd0a22545910cf68997b763e27f3569892b20dd528e70236c44b241c1d1b6c5fcec15a5a21f76af7e
|
data/.eslintrc
CHANGED
@@ -1,32 +1,7 @@
|
|
1
1
|
{
|
2
|
-
"
|
3
|
-
"extends": [
|
4
|
-
|
5
|
-
"
|
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
data/.hound.yml
CHANGED
data/.travis.yml
CHANGED
@@ -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 =
|
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.
|
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| [
|
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(
|
12
|
-
|
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,
|
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
|
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
|
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
|
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, :
|
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
|
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": "
|
7
|
-
"test": "
|
8
|
-
"test:watch": "
|
9
|
-
"test:current": "
|
10
|
-
|
11
|
-
|
12
|
-
"
|
13
|
-
"
|
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.
|
36
|
-
"@theforeman/
|
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
|
-
"
|
39
|
-
"
|
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.
|
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
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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.
|
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.
|
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 {
|
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 '
|
3
|
-
import AggregateStatus from './index
|
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,
|
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
|
7
|
+
import AggregateStatus from './AggregateStatus';
|
8
8
|
import * as JobInvocationActions from '../../redux/actions/jobInvocations';
|
9
9
|
|
10
|
-
const colIndexOfMaxValue = columns =>
|
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 {
|
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
|
26
|
-
|
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 =
|
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(
|
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 =
|
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
|
-
|
56
|
-
.
|
57
|
-
|
58
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
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(
|
22
|
-
|
23
|
-
|
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(
|
27
|
-
|
28
|
-
|
29
|
-
|
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(
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
+
expect(
|
37
|
+
reducer(pollingStarted, {
|
38
|
+
type: JOB_INVOCATIONS_GET_JOB_INVOCATIONS,
|
39
|
+
payload: jobInvocationsPayload,
|
40
|
+
})
|
41
|
+
).toEqual(jobInvocationsReceived);
|
36
42
|
});
|
37
43
|
});
|
data/webpack/test_setup.js
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
import 'core-js/shim';
|
2
2
|
import 'regenerator-runtime/runtime';
|
3
3
|
|
4
|
-
import { configure } from '
|
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.
|
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-
|
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.
|
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
|