foreman_remote_execution 4.5.1 → 4.5.2

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/ui_job_wizard_controller.rb +7 -0
  3. data/app/helpers/remote_execution_helper.rb +5 -1
  4. data/app/views/templates/ssh/module_action.erb +1 -0
  5. data/app/views/templates/ssh/puppet_run_once.erb +1 -0
  6. data/lib/foreman_remote_execution/version.rb +1 -1
  7. data/webpack/JobWizard/JobWizard.js +28 -8
  8. data/webpack/JobWizard/JobWizard.scss +39 -0
  9. data/webpack/JobWizard/JobWizardConstants.js +10 -0
  10. data/webpack/JobWizard/JobWizardSelectors.js +9 -0
  11. data/webpack/JobWizard/__tests__/fixtures.js +104 -2
  12. data/webpack/JobWizard/__tests__/integration.test.js +13 -85
  13. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +21 -4
  14. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +67 -0
  15. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +73 -59
  16. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +135 -16
  17. data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +23 -0
  18. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +122 -51
  19. data/webpack/JobWizard/steps/Schedule/QueryType.js +48 -0
  20. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +61 -0
  21. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +25 -0
  22. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +51 -0
  23. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +22 -0
  24. data/webpack/JobWizard/steps/Schedule/index.js +41 -0
  25. data/webpack/JobWizard/steps/form/FormHelpers.js +1 -0
  26. data/webpack/JobWizard/steps/form/Formatter.js +149 -0
  27. data/webpack/JobWizard/steps/form/NumberInput.js +33 -0
  28. data/webpack/JobWizard/steps/form/SelectField.js +14 -2
  29. data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +76 -0
  30. data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
  31. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
  32. metadata +13 -10
  33. data/webpack/JobWizard/__tests__/JobWizard.test.js +0 -13
  34. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -32
  35. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +0 -249
  36. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +0 -113
  37. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +0 -38
  38. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +0 -23
  39. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +0 -37
  40. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6f79124fa44d1ce9756c7c680a43ea7c9c1d309836641e4f1d91fe713a75fdb
4
- data.tar.gz: 1db640940ff3ba7a44b183c017ee0c1c3262e5263553365e930695b5f1686b8e
3
+ metadata.gz: 8c01aa4397a7c931f0cda22e96e238bded98642267efbd24d450fc27bb656d0a
4
+ data.tar.gz: 4a07bd69223c941761dc3ad16001eeef969612e3f5b648bd168f141e6f087781
5
5
  SHA512:
6
- metadata.gz: 9c105b218161cc5e509c5da55ca76d339d37d217e318aff139609361ece08ff0fa36c0b70fbb41e0c2bfac2633c86218fd4539e762e1c4f0e1ed49dc778ec728
7
- data.tar.gz: 9d148bc9b8af5f27c6b5bb91d8e28ea27bbff870ce1239a2e87413d5cf49b5740ccad18f713f22a1d348ff5fe185bdef015dd2bbd1b85f2efe802547e88fe193
6
+ metadata.gz: 56a50ffebf8864ec4851fc9e74c479a84b3af194502c5945e9c58cacc0068c283aab2d8cbc282a837aad686ebfe137477989fa572dce8e5d9914ae5c06b72736
7
+ data.tar.gz: c70b0c20e50b576841f96e8050b8be366d345758749b3c3d144af8e4364a65796d72a0874c382e3b13c5e73e36ef0d6266a1fa5544bf6b34805cb79de113c572
@@ -10,9 +10,12 @@ class UiJobWizardController < ::Api::V2::BaseController
10
10
 
11
11
  def template
12
12
  job_template = JobTemplate.authorized.find(params[:id])
13
+ advanced_template_inputs, template_inputs = map_template_inputs(job_template.template_inputs_with_foreign).partition { |x| x["advanced"] }
13
14
  render :json => {
14
15
  :job_template => job_template,
15
16
  :effective_user => job_template.effective_user,
17
+ :template_inputs => template_inputs,
18
+ :advanced_template_inputs => advanced_template_inputs,
16
19
  }
17
20
  end
18
21
 
@@ -20,6 +23,10 @@ class UiJobWizardController < ::Api::V2::BaseController
20
23
  nested_resource || 'job_template'
21
24
  end
22
25
 
26
+ def map_template_inputs(template_inputs_with_foreign)
27
+ template_inputs_with_foreign.map { |input| input.attributes.merge({:resource_type => input.resource_type&.tableize }) }
28
+ end
29
+
23
30
  def resource_class
24
31
  JobTemplate
25
32
  end
@@ -7,6 +7,10 @@ module RemoteExecutionHelper
7
7
  @job_hosts_authorizer ||= Authorizer.new(User.current, :collection => @hosts)
8
8
  end
9
9
 
10
+ def host_tasks_authorizer
11
+ @host_tasks_authorizer ||= Authorizer.new(User.current, :collection => @job_invocation.sub_tasks)
12
+ end
13
+
10
14
  def host_counter(label, count)
11
15
  content_tag(:div, :class => 'host_counter') do
12
16
  content_tag(:div, label, :class => 'header') + content_tag(:div, count.to_s, :class => 'count')
@@ -36,7 +40,7 @@ module RemoteExecutionHelper
36
40
  'data-method': 'get', id: "#{host.name}-actions-rerun" } }
37
41
  end
38
42
 
39
- if host_task.present? && authorized_for(hash_for_foreman_tasks_task_path(host_task).merge(auth_object: host_task, permission: :view_foreman_tasks))
43
+ if host_task.present? && authorized_for(hash_for_foreman_tasks_task_path(host_task).merge(auth_object: host_task, permission: :view_foreman_tasks, authorizer: host_tasks_authorizer))
40
44
  links << { title: _('Host task'),
41
45
  action: { href: foreman_tasks_task_path(host_task),
42
46
  'data-method': 'get', id: "#{host.name}-actions-task" } }
@@ -29,6 +29,7 @@ template_inputs:
29
29
  input_type: user
30
30
  required: false
31
31
  advanced: true
32
+ feature: :katello_module_stream_action
32
33
  %>
33
34
 
34
35
  <%
@@ -10,6 +10,7 @@ template_inputs:
10
10
  description: Additional options to pass to puppet
11
11
  input_type: user
12
12
  required: false
13
+ feature: puppet_run_host
13
14
  %>
14
15
  <% if @host.operatingsystem.family == 'Debian' -%>
15
16
  export PATH=/opt/puppetlabs/bin:$PATH
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '4.5.1'.freeze
2
+ VERSION = '4.5.2'.freeze
3
3
  end
@@ -1,3 +1,4 @@
1
+ /* eslint-disable camelcase */
1
2
  import React, { useState, useEffect, useCallback } from 'react';
2
3
  import { useDispatch, useSelector } from 'react-redux';
3
4
  import { Wizard } from '@patternfly/react-core';
@@ -8,6 +9,7 @@ import CategoryAndTemplate from './steps/CategoryAndTemplate/';
8
9
  import { AdvancedFields } from './steps/AdvancedFields/AdvancedFields';
9
10
  import { JOB_TEMPLATE } from './JobWizardConstants';
10
11
  import { selectTemplateError } from './JobWizardSelectors';
12
+ import Schedule from './steps/Schedule/';
11
13
  import './JobWizard.scss';
12
14
 
13
15
  export const JobWizard = () => {
@@ -16,13 +18,31 @@ export const JobWizard = () => {
16
18
  const [advancedValues, setAdvancedValues] = useState({});
17
19
  const dispatch = useDispatch();
18
20
 
19
- const setDefaults = useCallback(response => {
20
- const responseJob = response.data;
21
- setAdvancedValues({
22
- effectiveUserValue: responseJob.effective_user?.value || '',
23
- timeoutToKill: responseJob.job_template.execution_timeout_interval || '',
24
- });
25
- }, []);
21
+ const setDefaults = useCallback(
22
+ ({
23
+ data: {
24
+ advanced_template_inputs,
25
+ effective_user,
26
+ job_template: { executionTimeoutInterval, description_format },
27
+ },
28
+ }) => {
29
+ const advancedTemplateValues = {};
30
+ const advancedInputs = advanced_template_inputs;
31
+ if (advancedInputs) {
32
+ advancedInputs.forEach(input => {
33
+ advancedTemplateValues[input.name] = input?.default || '';
34
+ });
35
+ }
36
+ setAdvancedValues(currentAdvancedValues => ({
37
+ ...currentAdvancedValues,
38
+ effectiveUserValue: effective_user?.value || '',
39
+ timeoutToKill: executionTimeoutInterval || '',
40
+ templateValues: advancedTemplateValues,
41
+ description: description_format || '',
42
+ }));
43
+ },
44
+ []
45
+ );
26
46
  useEffect(() => {
27
47
  if (jobTemplateID) {
28
48
  dispatch(
@@ -72,7 +92,7 @@ export const JobWizard = () => {
72
92
  },
73
93
  {
74
94
  name: __('Schedule'),
75
- component: <p>Schedule</p>,
95
+ component: <Schedule />,
76
96
  canJumpTo: isTemplate,
77
97
  },
78
98
  {
@@ -10,5 +10,44 @@
10
10
  .advanced-fields-title {
11
11
  margin-bottom: 10px;
12
12
  }
13
+ #advanced-fields-job-template {
14
+ .foreman-search-field {
15
+ // Giving pf3 search bar a pf4 look
16
+ .search-bar {
17
+ display: block;
18
+ }
19
+ .input-group-btn {
20
+ display: none;
21
+ }
22
+ li {
23
+ font-size: 16px;
24
+ }
25
+ input {
26
+ font-size: 16px;
27
+ height: 36px;
28
+ }
29
+ .foreman-autocomplete .autocomplete-focus-shortcut {
30
+ top: 8px;
31
+ font-size: 16px;
32
+ }
33
+ .foreman-autocomplete .autocomplete-aux {
34
+ top: 8px;
35
+ font-size: 16px;
36
+ .autocomplete-clear-button {
37
+ font-size: 16px;
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ .schedule-tab {
45
+ input[type='radio'],
46
+ input[type='checkbox'] {
47
+ margin: 0;
48
+ }
49
+ .advanced-scheduling-button {
50
+ text-align: start;
51
+ }
13
52
  }
14
53
  }
@@ -1,6 +1,16 @@
1
+ import { translate as __ } from 'foremanReact/common/I18n';
1
2
  import { foremanUrl } from 'foremanReact/common/helpers';
2
3
 
3
4
  export const JOB_TEMPLATES = 'JOB_TEMPLATES';
4
5
  export const JOB_CATEGORIES = 'JOB_CATEGORIES';
5
6
  export const JOB_TEMPLATE = 'JOB_TEMPLATE';
6
7
  export const templatesUrl = foremanUrl('/api/v2/job_templates');
8
+
9
+ export const repeatTypes = {
10
+ noRepeat: __('Does not repeat'),
11
+ cronline: __('Cronline'),
12
+ monthly: __('Monthly'),
13
+ weekly: __('Weekly'),
14
+ daily: __('Daily'),
15
+ hourly: __('Hourly'),
16
+ };
@@ -36,3 +36,12 @@ export const selectTemplateError = state =>
36
36
 
37
37
  export const selectJobTemplate = state =>
38
38
  selectAPIResponse(state, JOB_TEMPLATE);
39
+
40
+ export const selectEffectiveUser = state =>
41
+ selectAPIResponse(state, JOB_TEMPLATE).effective_user;
42
+
43
+ export const selectAdvancedTemplateInputs = state =>
44
+ selectAPIResponse(state, JOB_TEMPLATE).advanced_template_inputs || [];
45
+
46
+ export const selectTemplateInputs = state =>
47
+ selectAPIResponse(state, JOB_TEMPLATE).template_inputs || [];
@@ -1,6 +1,8 @@
1
- const jobTemplate = {
1
+ import configureMockStore from 'redux-mock-store';
2
+
3
+ export const jobTemplate = {
2
4
  id: 178,
3
- name: 'Run Command - Ansible Default',
5
+ name: 'template1',
4
6
  template:
5
7
  "---\n- hosts: all\n tasks:\n - shell:\n cmd: |\n<%= indent(10) { input('command') } %>\n register: out\n - debug: var=out",
6
8
  snippet: false,
@@ -23,4 +25,104 @@ export const jobTemplateResponse = {
23
25
  overridable: true,
24
26
  current_user: false,
25
27
  },
28
+ advanced_template_inputs: [
29
+ {
30
+ name: 'adv plain hidden',
31
+ required: true,
32
+ input_type: 'user',
33
+ description: 'some Description',
34
+ advanced: true,
35
+ value_type: 'plain',
36
+ resource_type: 'ansible_roles',
37
+ default: 'Default val',
38
+ hidden_value: true,
39
+ },
40
+ {
41
+ name: 'adv plain select',
42
+ required: false,
43
+ input_type: 'user',
44
+ options: 'option 1\r\noption 2\r\noption 3\r\noption 4',
45
+ advanced: true,
46
+ value_type: 'plain',
47
+ resource_type: 'ansible_roles',
48
+ default: '',
49
+ hidden_value: false,
50
+ },
51
+ {
52
+ name: 'adv search',
53
+ required: false,
54
+ options: '',
55
+ advanced: true,
56
+ value_type: 'search',
57
+ resource_type: 'foreman_tasks/tasks',
58
+ default: '',
59
+ hidden_value: false,
60
+ },
61
+ {
62
+ name: 'adv date',
63
+ required: false,
64
+ options: '',
65
+ advanced: true,
66
+ value_type: 'date',
67
+ resource_type: 'ansible_roles',
68
+ default: '',
69
+ hidden_value: false,
70
+ },
71
+ ],
72
+ template_inputs: [
73
+ {
74
+ name: 'plain hidden',
75
+ required: true,
76
+ input_type: 'user',
77
+ description: 'some Description',
78
+ advanced: false,
79
+ value_type: 'plain',
80
+ resource_type: 'ansible_roles',
81
+ default: 'Default val',
82
+ hidden_value: true,
83
+ },
84
+ ],
85
+ };
86
+
87
+ export const jobCategories = ['Ansible Commands', 'Puppet', 'Services'];
88
+
89
+ export const testSetup = (selectors, api) => {
90
+ jest.spyOn(api, 'get');
91
+ jest.spyOn(selectors, 'selectJobTemplate');
92
+ jest.spyOn(selectors, 'selectJobTemplates');
93
+ jest.spyOn(selectors, 'selectJobCategories');
94
+ jest.spyOn(selectors, 'selectJobCategoriesStatus');
95
+
96
+ selectors.selectJobCategories.mockImplementation(() => jobCategories);
97
+ selectors.selectJobTemplates.mockImplementation(() => [
98
+ jobTemplate,
99
+ { ...jobTemplate, id: 2, name: 'template2' },
100
+ ]);
101
+ const mockStore = configureMockStore([]);
102
+ const store = mockStore({});
103
+ return store;
104
+ };
105
+
106
+ export const mockTemplate = selectors => {
107
+ selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
108
+ selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
109
+ };
110
+ export const mockApi = api => {
111
+ api.get.mockImplementation(({ handleSuccess, ...action }) => {
112
+ if (action.key === 'JOB_CATEGORIES') {
113
+ handleSuccess &&
114
+ handleSuccess({ data: { job_categories: jobCategories } });
115
+ } else if (action.key === 'JOB_TEMPLATE') {
116
+ handleSuccess &&
117
+ handleSuccess({
118
+ data: jobTemplateResponse,
119
+ });
120
+ } else if (action.key === 'JOB_TEMPLATES') {
121
+ handleSuccess &&
122
+ handleSuccess({
123
+ data: { results: [jobTemplate] },
124
+ });
125
+ }
126
+ return { type: 'get', ...action };
127
+ });
26
128
  };
@@ -1,40 +1,27 @@
1
1
  import React from 'react';
2
2
  import { Provider } from 'react-redux';
3
- import configureMockStore from 'redux-mock-store';
4
3
  import { mount } from '@theforeman/test';
5
4
  import { render, fireEvent, screen, act } from '@testing-library/react';
6
5
  import * as api from 'foremanReact/redux/API';
7
6
  import { JobWizard } from '../JobWizard';
8
7
  import * as selectors from '../JobWizardSelectors';
9
- import { jobTemplates, jobTemplateResponse as jobTemplate } from './fixtures';
8
+ import {
9
+ testSetup,
10
+ mockApi,
11
+ jobCategories,
12
+ jobTemplateResponse as jobTemplate,
13
+ } from './fixtures';
10
14
 
11
- jest.spyOn(api, 'get');
12
- jest.spyOn(selectors, 'selectJobTemplate');
13
- jest.spyOn(selectors, 'selectJobTemplates');
14
- jest.spyOn(selectors, 'selectJobCategories');
15
- jest.spyOn(selectors, 'selectJobCategoriesStatus');
15
+ const store = testSetup(selectors, api);
16
16
 
17
- const jobCategories = ['Ansible Commands', 'Puppet', 'Services'];
17
+ selectors.selectJobTemplate.mockImplementation(() => {});
18
18
 
19
19
  api.get.mockImplementation(({ handleSuccess, ...action }) => {
20
20
  if (action.key === 'JOB_CATEGORIES') {
21
21
  handleSuccess && handleSuccess({ data: { job_categories: jobCategories } });
22
- } else if (action.key === 'JOB_TEMPLATE') {
23
- handleSuccess &&
24
- handleSuccess({
25
- data: jobTemplate,
26
- });
27
22
  }
28
23
  return { type: 'get', ...action };
29
24
  });
30
-
31
- selectors.selectJobTemplate.mockImplementation(() => null);
32
- selectors.selectJobCategories.mockImplementation(() => jobCategories);
33
- selectors.selectJobCategoriesStatus.mockImplementation(() => null);
34
- selectors.selectJobTemplates.mockImplementation(() => jobTemplates);
35
-
36
- const mockStore = configureMockStore([]);
37
- const store = mockStore({});
38
25
  describe('Job wizard fill', () => {
39
26
  it('should select template', async () => {
40
27
  const wrapper = mount(
@@ -51,7 +38,10 @@ describe('Job wizard fill', () => {
51
38
  selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
52
39
  wrapper.find('.pf-c-button.pf-c-select__toggle-button').simulate('click');
53
40
  await act(async () => {
54
- await wrapper.find('.pf-c-select__menu-item').simulate('click');
41
+ await wrapper
42
+ .find('.pf-c-select__menu-item')
43
+ .first()
44
+ .simulate('click');
55
45
  await wrapper.update();
56
46
  });
57
47
  expect(store.getActions().slice(-1)).toMatchSnapshot('select template');
@@ -60,74 +50,12 @@ describe('Job wizard fill', () => {
60
50
  );
61
51
  });
62
52
 
63
- it('should save data between steps for advanced fields', async () => {
64
- const wrapper = mount(
65
- <Provider store={store}>
66
- <JobWizard advancedValues={{}} setAdvancedValues={jest.fn()} />
67
- </Provider>
68
- );
69
- // setup
70
- selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
71
- selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
72
- wrapper.find('.pf-c-button.pf-c-select__toggle-button').simulate('click');
73
- wrapper.find('.pf-c-select__menu-item').simulate('click');
74
-
75
- // test
76
- expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-disabled')).toHaveLength(
77
- 0
78
- );
79
- wrapper
80
- .find('.pf-c-wizard__nav-link')
81
- .at(2)
82
- .simulate('click'); // Advanced step
83
- const effectiveUserInput = () => wrapper.find('input#effective-user');
84
- const effectiveUesrValue = 'effective user new value';
85
- effectiveUserInput().getDOMNode().value = effectiveUesrValue;
86
- await act(async () => {
87
- await effectiveUserInput().simulate('change');
88
- wrapper.update();
89
- });
90
-
91
- expect(effectiveUserInput().prop('value')).toEqual(effectiveUesrValue);
92
-
93
- wrapper
94
- .find('.pf-c-wizard__nav-link')
95
- .at(1)
96
- .simulate('click');
97
-
98
- expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-current').text()).toEqual(
99
- 'Target Hosts'
100
- );
101
- wrapper
102
- .find('.pf-c-wizard__nav-link')
103
- .at(2)
104
- .simulate('click'); // Advanced step
105
-
106
- expect(effectiveUserInput().prop('value')).toEqual(effectiveUesrValue);
107
- });
108
-
109
53
  it('have all steps', async () => {
110
54
  selectors.selectJobCategoriesStatus.mockImplementation(() => null);
111
55
  selectors.selectJobTemplate.mockRestore();
112
56
  selectors.selectJobTemplates.mockRestore();
113
57
  selectors.selectJobCategories.mockRestore();
114
- api.get.mockImplementation(({ handleSuccess, ...action }) => {
115
- if (action.key === 'JOB_CATEGORIES') {
116
- handleSuccess &&
117
- handleSuccess({ data: { job_categories: jobCategories } });
118
- } else if (action.key === 'JOB_TEMPLATE') {
119
- handleSuccess &&
120
- handleSuccess({
121
- data: jobTemplate,
122
- });
123
- } else if (action.key === 'JOB_TEMPLATES') {
124
- handleSuccess &&
125
- handleSuccess({
126
- data: { results: [jobTemplate.job_template] },
127
- });
128
- }
129
- return { type: 'get', ...action };
130
- });
58
+ mockApi(api);
131
59
 
132
60
  render(
133
61
  <Provider store={store}>