foreman_remote_execution 16.5.2 → 16.5.3
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/app/models/concerns/foreman_remote_execution/host_extensions.rb +2 -2
- data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +4 -4
- data/app/views/api/v2/smart_proxies/ca_pubkey.json.rabl +1 -1
- data/app/views/api/v2/smart_proxies/pubkey.json.rabl +1 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/unit/concerns/host_extensions_test.rb +28 -4
- data/test/unit/concerns/smart_proxy_extensions_test.rb +83 -0
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js +60 -0
- data/webpack/JobWizard/JobWizard.js +7 -1
- data/webpack/JobWizard/JobWizardSelectors.js +31 -15
- data/webpack/JobWizard/__tests__/fixtures.js +29 -13
- data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +2 -2
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4d4a947b587075d0d60662d365b36c3fab8006b958a363c39354b1f67f771c69
|
|
4
|
+
data.tar.gz: 16bc29adc59c2a3bf1605902aece3e9d0f2bbd67749f4bc1a429ae52507165c8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3766b56254d55dd3f520ca16cca021d558362db4a34ccb15fb6833124a14f5900deefc78fc4125b4128c28f8eafd6d41f9c97d5526702a66ff32eb28665cde6f
|
|
7
|
+
data.tar.gz: 1cb89677b91f21c07296af85bd5b18c993572965e701d8b8e55d0880ff5e5fc81b7d7a56a6a053a916309825489c3ec90d929092382f5dd1630fb44fd599a6fc
|
|
@@ -109,11 +109,11 @@ module ForemanRemoteExecution
|
|
|
109
109
|
|
|
110
110
|
def remote_execution_ssh_keys
|
|
111
111
|
# only include public keys from SSH proxies that don't have SSH cert verification configured
|
|
112
|
-
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.pubkey if proxy.ca_pubkey.blank? }.compact.uniq
|
|
112
|
+
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.pubkey(refresh: false) if proxy.ca_pubkey(refresh: false).blank? }.compact.uniq
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
def remote_execution_ssh_ca_keys
|
|
116
|
-
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.ca_pubkey }.compact.uniq
|
|
116
|
+
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.ca_pubkey(refresh: false) }.compact.uniq
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
def drop_execution_interface_cache
|
|
@@ -7,12 +7,12 @@ module ForemanRemoteExecution
|
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def pubkey
|
|
11
|
-
self[:pubkey] || update_pubkey
|
|
10
|
+
def pubkey(refresh: true)
|
|
11
|
+
self[:pubkey] || (refresh && update_pubkey || nil)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def ca_pubkey
|
|
15
|
-
self[:ca_pubkey] || update_ca_pubkey
|
|
14
|
+
def ca_pubkey(refresh: true)
|
|
15
|
+
self[:ca_pubkey] || (refresh && update_ca_pubkey || nil)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def update_pubkey
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
node(:remote_execution_ca_pubkey) { |p| p.ca_pubkey(refresh: false) }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
node(:remote_execution_pubkey) { |p| p.pubkey(refresh: false) }
|
|
@@ -11,8 +11,9 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
|
|
|
11
11
|
let(:sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQ foo@example.com' }
|
|
12
12
|
|
|
13
13
|
before do
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
host.subnet.remote_execution_proxies.each do |proxy|
|
|
15
|
+
proxy.update(pubkey: sshkey, ca_pubkey: nil)
|
|
16
|
+
end
|
|
16
17
|
Setting[:remote_execution_ssh_user] = 'root'
|
|
17
18
|
Setting[:remote_execution_effective_user_method] = 'sudo'
|
|
18
19
|
end
|
|
@@ -60,6 +61,17 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
|
|
|
60
61
|
User.current = nil
|
|
61
62
|
assert_includes host.remote_execution_ssh_keys, sshkey
|
|
62
63
|
end
|
|
64
|
+
|
|
65
|
+
it 'triggers no calls to the proxy' do
|
|
66
|
+
host.subnet.remote_execution_proxies.each do |proxy|
|
|
67
|
+
proxy.update(pubkey: nil)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
SmartProxy.any_instance.expects(:update_pubkey).never
|
|
71
|
+
SmartProxy.any_instance.expects(:update_ca_pubkey).never
|
|
72
|
+
|
|
73
|
+
host.host_param('remote_execution_ssh_keys')
|
|
74
|
+
end
|
|
63
75
|
end
|
|
64
76
|
|
|
65
77
|
describe 'has ssh CA key configured' do
|
|
@@ -68,8 +80,9 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
|
|
|
68
80
|
let(:ca_sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJE bar@example.com' }
|
|
69
81
|
|
|
70
82
|
before do
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
host.subnet.remote_execution_proxies.each do |proxy|
|
|
84
|
+
proxy.update(pubkey: sshkey, ca_pubkey: ca_sshkey)
|
|
85
|
+
end
|
|
73
86
|
Setting[:remote_execution_ssh_user] = 'root'
|
|
74
87
|
Setting[:remote_execution_effective_user_method] = 'sudo'
|
|
75
88
|
end
|
|
@@ -102,6 +115,17 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
|
|
|
102
115
|
assert_includes host.host_param('remote_execution_ssh_ca_keys'), key
|
|
103
116
|
assert_includes host.host_param('remote_execution_ssh_ca_keys'), ca_sshkey
|
|
104
117
|
end
|
|
118
|
+
|
|
119
|
+
it 'triggers no calls to the proxy' do
|
|
120
|
+
host.subnet.remote_execution_proxies.each do |proxy|
|
|
121
|
+
proxy.update(ca_pubkey: nil)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
SmartProxy.any_instance.expects(:update_pubkey).never
|
|
125
|
+
SmartProxy.any_instance.expects(:update_ca_pubkey).never
|
|
126
|
+
|
|
127
|
+
host.host_param('remote_execution_ssh_ca_keys')
|
|
128
|
+
end
|
|
105
129
|
end
|
|
106
130
|
|
|
107
131
|
context 'host has multiple nics' do
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require 'test_plugin_helper'
|
|
2
|
+
|
|
3
|
+
class ForemanRemoteExecutionSmartProxyExtensionsTest < ActiveSupport::TestCase
|
|
4
|
+
let(:proxy) { FactoryBot.create(:smart_proxy, :ssh) }
|
|
5
|
+
|
|
6
|
+
describe '#pubkey' do
|
|
7
|
+
context 'when key is cached in the database' do
|
|
8
|
+
it 'returns the cached key without fetching from proxy' do
|
|
9
|
+
proxy.expects(:update_pubkey).never
|
|
10
|
+
assert_equal 'ssh-rsa AAAAB3N...', proxy.pubkey
|
|
11
|
+
assert_equal 'ssh-rsa AAAAB3N...', proxy.pubkey(refresh: false)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
context 'when key is not cached' do
|
|
16
|
+
before { proxy.update(pubkey: nil) }
|
|
17
|
+
|
|
18
|
+
it 'fetches from proxy by default' do
|
|
19
|
+
proxy.expects(:update_pubkey).returns('ssh-rsa FETCHED...')
|
|
20
|
+
assert_equal 'ssh-rsa FETCHED...', proxy.pubkey
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'returns nil without fetching when refresh: false' do
|
|
24
|
+
proxy.expects(:update_pubkey).never
|
|
25
|
+
assert_nil proxy.pubkey(refresh: false)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe '#ca_pubkey' do
|
|
31
|
+
context 'when key is cached in the database' do
|
|
32
|
+
before { proxy.update(ca_pubkey: 'ssh-rsa CA_KEY...') }
|
|
33
|
+
|
|
34
|
+
it 'returns the cached key without fetching from proxy' do
|
|
35
|
+
proxy.expects(:update_ca_pubkey).never
|
|
36
|
+
assert_equal 'ssh-rsa CA_KEY...', proxy.ca_pubkey
|
|
37
|
+
assert_equal 'ssh-rsa CA_KEY...', proxy.ca_pubkey(refresh: false)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context 'when key is not cached' do
|
|
42
|
+
before { proxy.update(ca_pubkey: nil) }
|
|
43
|
+
|
|
44
|
+
it 'fetches from proxy by default' do
|
|
45
|
+
proxy.expects(:update_ca_pubkey).returns('ssh-rsa FETCHED_CA...')
|
|
46
|
+
assert_equal 'ssh-rsa FETCHED_CA...', proxy.ca_pubkey
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'returns nil without fetching when refresh: false' do
|
|
50
|
+
proxy.expects(:update_ca_pubkey).never
|
|
51
|
+
assert_nil proxy.ca_pubkey(refresh: false)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe '#refresh' do
|
|
57
|
+
context 'when the key is cached' do
|
|
58
|
+
before do
|
|
59
|
+
proxy.update(pubkey: 'ssh-rsa KEY...', ca_pubkey: 'ssh-rsa CA_KEY...')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'fetches the keys' do
|
|
63
|
+
proxy.expects(:update_pubkey).once
|
|
64
|
+
proxy.expects(:update_ca_pubkey).once
|
|
65
|
+
|
|
66
|
+
proxy.refresh
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
context 'when the key is not cached' do
|
|
71
|
+
before do
|
|
72
|
+
proxy.update(pubkey: nil, ca_pubkey: nil)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'fetches the keys' do
|
|
76
|
+
proxy.expects(:update_pubkey).once
|
|
77
|
+
proxy.expects(:update_ca_pubkey).once
|
|
78
|
+
|
|
79
|
+
proxy.refresh
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -11,6 +11,10 @@ import { mockTemplateInvocationResponse } from './fixtures';
|
|
|
11
11
|
jest.spyOn(api, 'get');
|
|
12
12
|
jest.mock('../JobInvocationSelectors');
|
|
13
13
|
|
|
14
|
+
jest.mock('foremanReact/components/ToastsList', () => ({
|
|
15
|
+
addToast: jest.fn(payload => ({ type: 'ADD_TOAST', payload })),
|
|
16
|
+
}));
|
|
17
|
+
|
|
14
18
|
const mockStore = configureMockStore([]);
|
|
15
19
|
const store = mockStore({
|
|
16
20
|
HOSTS_API: {
|
|
@@ -186,4 +190,60 @@ describe('TemplateInvocation', () => {
|
|
|
186
190
|
await screen.findByText('Successfully copied to clipboard!')
|
|
187
191
|
).toBeInTheDocument();
|
|
188
192
|
});
|
|
193
|
+
|
|
194
|
+
describe('Cancel/Abort task buttons API calls', () => {
|
|
195
|
+
const responseWithCancellableTask = {
|
|
196
|
+
...mockTemplateInvocationResponse,
|
|
197
|
+
task: { id: 'task-123', cancellable: true },
|
|
198
|
+
permissions: {
|
|
199
|
+
view_foreman_tasks: true,
|
|
200
|
+
cancel_job_invocations: true,
|
|
201
|
+
execute_jobs: true,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
beforeEach(() => {
|
|
206
|
+
selectors.selectTemplateInvocationStatus.mockImplementation(() => () =>
|
|
207
|
+
'RESOLVED'
|
|
208
|
+
);
|
|
209
|
+
selectors.selectTemplateInvocation.mockImplementation(() => () =>
|
|
210
|
+
responseWithCancellableTask
|
|
211
|
+
);
|
|
212
|
+
jest.spyOn(api.APIActions, 'post').mockReturnValue({ type: 'MOCK_POST' });
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('clicking the `Cancel Task` button calls API with cancel param', () => {
|
|
216
|
+
render(
|
|
217
|
+
<Provider store={store}>
|
|
218
|
+
<TemplateInvocation {...mockProps} />
|
|
219
|
+
</Provider>
|
|
220
|
+
);
|
|
221
|
+
fireEvent.click(screen.getByText('Cancel Task'));
|
|
222
|
+
|
|
223
|
+
const postCall = api.APIActions.post.mock.calls.find(
|
|
224
|
+
call => call[0].key === 'CANCEL_TASK'
|
|
225
|
+
)?.[0];
|
|
226
|
+
expect(postCall.url).toBe(
|
|
227
|
+
`/foreman_tasks/tasks/${responseWithCancellableTask.task.id}/cancel`
|
|
228
|
+
);
|
|
229
|
+
expect(postCall.key).toBe('CANCEL_TASK');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('clicking the `Abort Task` button calls API with abort param', () => {
|
|
233
|
+
render(
|
|
234
|
+
<Provider store={store}>
|
|
235
|
+
<TemplateInvocation {...mockProps} />
|
|
236
|
+
</Provider>
|
|
237
|
+
);
|
|
238
|
+
fireEvent.click(screen.getByText('Abort task'));
|
|
239
|
+
|
|
240
|
+
const postCall = api.APIActions.post.mock.calls.find(
|
|
241
|
+
call => call[0].key === 'ABORT_TASK'
|
|
242
|
+
)?.[0];
|
|
243
|
+
expect(postCall.url).toBe(
|
|
244
|
+
`/foreman_tasks/tasks/${responseWithCancellableTask.task.id}/abort`
|
|
245
|
+
);
|
|
246
|
+
expect(postCall.key).toBe('ABORT_TASK');
|
|
247
|
+
});
|
|
248
|
+
});
|
|
189
249
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable max-lines */
|
|
2
2
|
/* eslint-disable camelcase */
|
|
3
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
3
|
+
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
4
4
|
import { useDispatch, useSelector } from 'react-redux';
|
|
5
5
|
import PropTypes from 'prop-types';
|
|
6
6
|
import { Wizard } from '@patternfly/react-core/deprecated';
|
|
@@ -51,6 +51,8 @@ export const JobWizard = ({ rerunData }) => {
|
|
|
51
51
|
const [category, setCategory] = useState(
|
|
52
52
|
rerunData?.job_category || jobCategoriesResponse?.default_category || ''
|
|
53
53
|
);
|
|
54
|
+
const categoryRef = useRef(category);
|
|
55
|
+
categoryRef.current = category;
|
|
54
56
|
const [advancedValues, setAdvancedValues] = useState({ templateValues: {} });
|
|
55
57
|
const [templateValues, setTemplateValues] = useState({});
|
|
56
58
|
const [scheduleValue, setScheduleValue] = useState(initialScheduleState);
|
|
@@ -89,6 +91,10 @@ export const JobWizard = ({ rerunData }) => {
|
|
|
89
91
|
concurrency_control = {},
|
|
90
92
|
},
|
|
91
93
|
}) => {
|
|
94
|
+
if (categoryRef.current !== job_category) {
|
|
95
|
+
setCategory(job_category);
|
|
96
|
+
}
|
|
97
|
+
|
|
92
98
|
const advancedTemplateValues = {};
|
|
93
99
|
const defaultTemplateValues = {};
|
|
94
100
|
const inputs = template_inputs;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
1
2
|
import URI from 'urijs';
|
|
2
3
|
import { get } from 'lodash';
|
|
4
|
+
import { createSelector } from 'reselect';
|
|
3
5
|
import {
|
|
4
6
|
selectAPIResponse,
|
|
5
7
|
selectAPIStatus,
|
|
@@ -17,8 +19,12 @@ import {
|
|
|
17
19
|
JOB_API_KEY,
|
|
18
20
|
} from './JobWizardConstants';
|
|
19
21
|
|
|
22
|
+
/** Stable fallbacks so useSelector does not see a new reference every run */
|
|
23
|
+
const EMPTY_ARRAY = [];
|
|
24
|
+
const EMPTY_OBJECT = {};
|
|
25
|
+
|
|
20
26
|
export const selectRerunJobInvocationResponse = state =>
|
|
21
|
-
selectAPIResponse(state, JOB_API_KEY) ||
|
|
27
|
+
selectAPIResponse(state, JOB_API_KEY) || EMPTY_OBJECT;
|
|
22
28
|
|
|
23
29
|
export const selectRerunJobInvocationStatus = state =>
|
|
24
30
|
selectAPIStatus(state, JOB_API_KEY);
|
|
@@ -27,19 +33,26 @@ export const selectJobTemplatesStatus = state =>
|
|
|
27
33
|
selectAPIStatus(state, JOB_TEMPLATES);
|
|
28
34
|
|
|
29
35
|
export const filterJobTemplates = templates =>
|
|
30
|
-
templates?.filter(template => !template.snippet) ||
|
|
36
|
+
templates?.filter(template => !template.snippet) || EMPTY_ARRAY;
|
|
37
|
+
|
|
38
|
+
const selectJobTemplatesResults = state =>
|
|
39
|
+
selectAPIResponse(state, JOB_TEMPLATES)?.results;
|
|
31
40
|
|
|
32
|
-
export const selectJobTemplates =
|
|
33
|
-
|
|
41
|
+
export const selectJobTemplates = createSelector(
|
|
42
|
+
[selectJobTemplatesResults],
|
|
43
|
+
results => filterJobTemplates(results)
|
|
44
|
+
);
|
|
34
45
|
|
|
35
46
|
export const selectJobTemplatesSearch = state =>
|
|
36
47
|
selectAPIResponse(state, JOB_TEMPLATES)?.search;
|
|
37
48
|
|
|
38
49
|
export const selectJobCategoriesResponse = state =>
|
|
39
|
-
selectAPIResponse(state, JOB_CATEGORIES) ||
|
|
50
|
+
selectAPIResponse(state, JOB_CATEGORIES) || EMPTY_OBJECT;
|
|
40
51
|
|
|
41
|
-
export const selectJobCategories = state =>
|
|
42
|
-
selectJobCategoriesResponse(state)
|
|
52
|
+
export const selectJobCategories = state => {
|
|
53
|
+
const { job_categories: jobCategories } = selectJobCategoriesResponse(state);
|
|
54
|
+
return jobCategories || EMPTY_ARRAY;
|
|
55
|
+
};
|
|
43
56
|
|
|
44
57
|
export const selectWithKatello = state =>
|
|
45
58
|
selectJobCategoriesResponse(state).with_katello || false;
|
|
@@ -58,7 +71,7 @@ export const selectJobCategoriesMissingPermissions = state => {
|
|
|
58
71
|
'data',
|
|
59
72
|
'error',
|
|
60
73
|
'missing_permissions',
|
|
61
|
-
]) ||
|
|
74
|
+
]) || EMPTY_ARRAY
|
|
62
75
|
);
|
|
63
76
|
};
|
|
64
77
|
|
|
@@ -75,29 +88,32 @@ export const selectEffectiveUser = state =>
|
|
|
75
88
|
selectAPIResponse(state, JOB_TEMPLATE).effective_user;
|
|
76
89
|
|
|
77
90
|
export const selectAdvancedTemplateInputs = state =>
|
|
78
|
-
selectAPIResponse(state, JOB_TEMPLATE)
|
|
91
|
+
selectAPIResponse(state, JOB_TEMPLATE)?.advanced_template_inputs ||
|
|
92
|
+
EMPTY_ARRAY;
|
|
79
93
|
|
|
80
94
|
export const selectTemplateInputs = state =>
|
|
81
|
-
selectAPIResponse(state, JOB_TEMPLATE)
|
|
95
|
+
selectAPIResponse(state, JOB_TEMPLATE)?.template_inputs || EMPTY_ARRAY;
|
|
82
96
|
|
|
83
97
|
export const selectHostsResponse = state => selectAPIResponse(state, HOSTS_API);
|
|
84
98
|
|
|
85
99
|
export const selectHostCount = state =>
|
|
86
100
|
selectHostsResponse(state).subtotal || 0;
|
|
87
101
|
|
|
88
|
-
|
|
89
|
-
|
|
102
|
+
const selectHostsResults = state => selectHostsResponse(state).results;
|
|
103
|
+
|
|
104
|
+
export const selectHosts = createSelector([selectHostsResults], results => {
|
|
105
|
+
const hosts = results || EMPTY_ARRAY;
|
|
90
106
|
return hosts.map(host => ({
|
|
91
107
|
name: host.name,
|
|
92
108
|
display_name: host.display_name,
|
|
93
109
|
}));
|
|
94
|
-
};
|
|
110
|
+
});
|
|
95
111
|
|
|
96
112
|
export const selectHostsMissingPermissions = state => {
|
|
97
113
|
const hostsResponse = selectHostsResponse(state);
|
|
98
114
|
return (
|
|
99
115
|
get(hostsResponse, ['response', 'data', 'error', 'missing_permissions']) ||
|
|
100
|
-
|
|
116
|
+
EMPTY_ARRAY
|
|
101
117
|
);
|
|
102
118
|
};
|
|
103
119
|
|
|
@@ -115,6 +131,6 @@ export const selectIsSubmitting = state =>
|
|
|
115
131
|
selectAPIStatus(state, JOB_INVOCATION) === STATUS.RESOLVED;
|
|
116
132
|
|
|
117
133
|
export const selectRouterSearch = state => {
|
|
118
|
-
const { search } = selectRouterLocation(state);
|
|
134
|
+
const { search } = selectRouterLocation(state) || {};
|
|
119
135
|
return URI.parseQuery(search);
|
|
120
136
|
};
|
|
@@ -30,6 +30,12 @@ export const pupptetJobTemplate = {
|
|
|
30
30
|
|
|
31
31
|
export const jobTemplates = [jobTemplate];
|
|
32
32
|
|
|
33
|
+
export const wizardJobTemplatesMockList = [
|
|
34
|
+
jobTemplate,
|
|
35
|
+
pupptetJobTemplate,
|
|
36
|
+
{ ...jobTemplate, id: 2, name: 'template2' },
|
|
37
|
+
];
|
|
38
|
+
|
|
33
39
|
export const jobTemplateResponse = {
|
|
34
40
|
job_template: jobTemplate,
|
|
35
41
|
effective_user: {
|
|
@@ -137,11 +143,9 @@ export const testSetup = (selectors, api) => {
|
|
|
137
143
|
() => jobTemplateResponse.advanced_template_inputs
|
|
138
144
|
);
|
|
139
145
|
selectors.selectJobCategories.mockImplementation(() => jobCategories);
|
|
140
|
-
selectors.selectJobTemplates.mockImplementation(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
{ ...jobTemplate, id: 2, name: 'template2' },
|
|
144
|
-
]);
|
|
146
|
+
selectors.selectJobTemplates.mockImplementation(
|
|
147
|
+
() => wizardJobTemplatesMockList
|
|
148
|
+
);
|
|
145
149
|
selectors.selectJobTemplate.mockImplementation(() => jobTemplateResponse);
|
|
146
150
|
|
|
147
151
|
selectors.selectEffectiveUser.mockImplementation(
|
|
@@ -187,13 +191,25 @@ export const mockApi = api => {
|
|
|
187
191
|
},
|
|
188
192
|
});
|
|
189
193
|
} else if (action.key === 'JOB_TEMPLATE') {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
194
|
+
const url = String(action.url);
|
|
195
|
+
let templatePayload = jobTemplateResponse;
|
|
196
|
+
if (url.includes('/template/163')) {
|
|
197
|
+
templatePayload = {
|
|
198
|
+
...jobTemplateResponse,
|
|
199
|
+
job_template: pupptetJobTemplate,
|
|
200
|
+
};
|
|
201
|
+
} else if (url.includes('/template/2')) {
|
|
202
|
+
const jobTemplate2 = { ...jobTemplate, id: 2, name: 'template2' };
|
|
203
|
+
templatePayload = {
|
|
204
|
+
...jobTemplateResponse,
|
|
205
|
+
job_template: jobTemplate2,
|
|
206
|
+
effective_user: {
|
|
207
|
+
...jobTemplateResponse.effective_user,
|
|
208
|
+
job_template_id: 2,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
handleSuccess && handleSuccess({ data: templatePayload });
|
|
197
213
|
} else if (action.key === 'JOB_TEMPLATES') {
|
|
198
214
|
handleSuccess &&
|
|
199
215
|
handleSuccess({
|
|
@@ -202,7 +218,7 @@ export const mockApi = api => {
|
|
|
202
218
|
action.url.search() ===
|
|
203
219
|
'?search=job_category%3D%22Puppet%22&per_page=all'
|
|
204
220
|
? [pupptetJobTemplate]
|
|
205
|
-
:
|
|
221
|
+
: wizardJobTemplatesMockList,
|
|
206
222
|
},
|
|
207
223
|
});
|
|
208
224
|
} else if (action.key === 'HOST_IDS') {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect } from 'react';
|
|
1
|
+
import React, { useEffect, memo } from 'react';
|
|
2
2
|
import { useSelector, useDispatch } from 'react-redux';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
import URI from 'urijs';
|
|
@@ -133,4 +133,4 @@ ConnectedCategoryAndTemplate.propTypes = {
|
|
|
133
133
|
};
|
|
134
134
|
ConnectedCategoryAndTemplate.defaultProps = { jobTemplate: null };
|
|
135
135
|
|
|
136
|
-
export default ConnectedCategoryAndTemplate;
|
|
136
|
+
export default memo(ConnectedCategoryAndTemplate);
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foreman_remote_execution
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 16.5.
|
|
4
|
+
version: 16.5.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Foreman Remote Execution team
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-03-
|
|
10
|
+
date: 2026-03-31 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: deface
|
|
@@ -390,6 +390,7 @@ files:
|
|
|
390
390
|
- test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb
|
|
391
391
|
- test/unit/concerns/host_extensions_test.rb
|
|
392
392
|
- test/unit/concerns/nic_extensions_test.rb
|
|
393
|
+
- test/unit/concerns/smart_proxy_extensions_test.rb
|
|
393
394
|
- test/unit/execution_task_status_mapper_test.rb
|
|
394
395
|
- test/unit/input_template_renderer_test.rb
|
|
395
396
|
- test/unit/job_invocation_composer_test.rb
|