foreman_remote_execution 4.3.0 → 4.5.1

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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +27 -22
  3. data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_commands_controller_extensions.rb +19 -0
  4. data/app/controllers/job_invocations_controller.rb +1 -1
  5. data/app/controllers/job_templates_controller.rb +4 -4
  6. data/app/controllers/ui_job_wizard_controller.rb +12 -0
  7. data/app/helpers/job_invocations_helper.rb +2 -2
  8. data/app/helpers/remote_execution_helper.rb +35 -8
  9. data/app/lib/actions/remote_execution/run_host_job.rb +37 -7
  10. data/app/lib/foreman_remote_execution/provider_input.rb +29 -0
  11. data/app/lib/foreman_remote_execution/renderer/scope/input.rb +1 -0
  12. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -5
  13. data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
  14. data/app/models/host_proxy_invocation.rb +4 -0
  15. data/app/models/host_status/execution_status.rb +5 -5
  16. data/app/models/invocation_provider_input_value.rb +12 -0
  17. data/app/models/job_invocation.rb +35 -12
  18. data/app/models/job_invocation_composer.rb +74 -19
  19. data/app/models/remote_execution_provider.rb +18 -3
  20. data/app/models/setting/remote_execution.rb +11 -1
  21. data/app/models/ssh_execution_provider.rb +4 -4
  22. data/app/models/targeting.rb +5 -1
  23. data/app/models/template_invocation.rb +2 -0
  24. data/app/overrides/execution_interface.rb +8 -8
  25. data/app/overrides/subnet_proxies.rb +6 -6
  26. data/app/services/renderer_methods.rb +12 -0
  27. data/app/views/job_invocations/_form.html.erb +8 -0
  28. data/app/views/job_invocations/index.html.erb +1 -1
  29. data/config/routes.rb +1 -0
  30. data/db/migrate/20180110104432_rename_template_invocation_permission.rb +1 -1
  31. data/db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb +4 -4
  32. data/db/migrate/20210312074713_add_provider_inputs.rb +10 -0
  33. data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
  34. data/extra/cockpit/foreman-cockpit-session +6 -6
  35. data/foreman_remote_execution.gemspec +1 -1
  36. data/lib/foreman_remote_execution/engine.rb +14 -12
  37. data/lib/foreman_remote_execution/version.rb +1 -1
  38. data/lib/tasks/foreman_remote_execution_tasks.rake +1 -18
  39. data/locale/action_names.rb +1 -0
  40. data/locale/de/foreman_remote_execution.po +77 -27
  41. data/locale/en/foreman_remote_execution.po +77 -27
  42. data/locale/en_GB/foreman_remote_execution.po +77 -27
  43. data/locale/es/foreman_remote_execution.po +77 -27
  44. data/locale/foreman_remote_execution.pot +241 -163
  45. data/locale/fr/foreman_remote_execution.po +77 -27
  46. data/locale/ja/foreman_remote_execution.po +77 -27
  47. data/locale/ko/foreman_remote_execution.po +77 -27
  48. data/locale/pt_BR/foreman_remote_execution.po +77 -27
  49. data/locale/ru/foreman_remote_execution.po +77 -27
  50. data/locale/zh_CN/foreman_remote_execution.po +77 -27
  51. data/locale/zh_TW/foreman_remote_execution.po +77 -27
  52. data/package.json +4 -2
  53. data/test/functional/api/v2/job_invocations_controller_test.rb +14 -1
  54. data/test/helpers/remote_execution_helper_test.rb +16 -0
  55. data/test/unit/job_invocation_composer_test.rb +100 -3
  56. data/test/unit/job_invocation_report_template_test.rb +57 -0
  57. data/test/unit/job_invocation_test.rb +1 -1
  58. data/webpack/JobWizard/JobWizard.js +75 -11
  59. data/webpack/JobWizard/JobWizard.scss +14 -0
  60. data/webpack/JobWizard/JobWizardConstants.js +6 -0
  61. data/webpack/JobWizard/JobWizardSelectors.js +38 -0
  62. data/webpack/JobWizard/__tests__/JobWizard.test.js +13 -0
  63. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +32 -0
  64. data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +43 -0
  65. data/webpack/JobWizard/__tests__/fixtures.js +26 -0
  66. data/webpack/JobWizard/__tests__/integration.test.js +156 -0
  67. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +93 -0
  68. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +181 -0
  69. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +25 -0
  70. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +249 -0
  71. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +109 -0
  72. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +52 -0
  73. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +113 -0
  74. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +94 -0
  75. data/webpack/JobWizard/steps/form/FormHelpers.js +19 -0
  76. data/webpack/JobWizard/steps/form/GroupedSelectField.js +91 -0
  77. data/webpack/JobWizard/steps/form/SelectField.js +48 -0
  78. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +38 -0
  79. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +23 -0
  80. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +37 -0
  81. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +23 -0
  82. data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
  83. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +21 -2
  84. data/webpack/__mocks__/foremanReact/redux/API/index.js +5 -0
  85. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
  86. data/webpack/global_index.js +6 -0
  87. data/webpack/index.js +3 -4
  88. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +83 -0
  89. data/webpack/react_app/components/RecentJobsCard/constants.js +1 -0
  90. data/webpack/react_app/components/RecentJobsCard/index.js +1 -0
  91. data/webpack/react_app/components/RecentJobsCard/styles.css +15 -0
  92. data/webpack/react_app/components/RegistrationExtension/RexInterface.js +50 -0
  93. data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +9 -0
  94. data/webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap +35 -0
  95. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +8 -3
  96. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +7 -2
  97. data/webpack/react_app/extend/fillRecentJobsCard.js +11 -0
  98. data/webpack/react_app/extend/fillregistrationAdvanced.js +11 -0
  99. data/webpack/react_app/extend/reducers.js +5 -0
  100. metadata +49 -8
  101. data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
  102. data/app/views/api/v2/registration/_form.html.erb +0 -12
  103. data/test/models/orchestration/ssh_test.rb +0 -56
@@ -0,0 +1,48 @@
1
+ import React, { useState } from 'react';
2
+ import { FormGroup, Select, SelectOption } from '@patternfly/react-core';
3
+ import PropTypes from 'prop-types';
4
+
5
+ export const SelectField = ({
6
+ label,
7
+ fieldId,
8
+ options,
9
+ value,
10
+ setValue,
11
+ ...props
12
+ }) => {
13
+ const onSelect = (event, selection) => {
14
+ setValue(selection);
15
+ setIsOpen(false);
16
+ };
17
+ const [isOpen, setIsOpen] = useState(false);
18
+ return (
19
+ <FormGroup label={label} fieldId={fieldId}>
20
+ <Select
21
+ selections={value}
22
+ onSelect={onSelect}
23
+ onToggle={setIsOpen}
24
+ isOpen={isOpen}
25
+ className="without_select2"
26
+ maxHeight="45vh"
27
+ menuAppendTo={() => document.body}
28
+ {...props}
29
+ >
30
+ {options.map((option, index) => (
31
+ <SelectOption key={index} value={option} />
32
+ ))}
33
+ </Select>
34
+ </FormGroup>
35
+ );
36
+ };
37
+ SelectField.propTypes = {
38
+ label: PropTypes.string.isRequired,
39
+ fieldId: PropTypes.string.isRequired,
40
+ options: PropTypes.array,
41
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
42
+ setValue: PropTypes.func.isRequired,
43
+ };
44
+
45
+ SelectField.defaultProps = {
46
+ options: [],
47
+ value: null,
48
+ };
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import * as patternfly from '@patternfly/react-core';
3
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
4
+ import { GroupedSelectField } from '../GroupedSelectField';
5
+
6
+ jest.spyOn(patternfly, 'Select');
7
+ jest.spyOn(patternfly, 'SelectOption');
8
+ patternfly.Select.mockImplementation(props => <div>{props}</div>);
9
+ patternfly.SelectOption.mockImplementation(props => <div>{props}</div>);
10
+
11
+ const fixtures = {
12
+ 'renders with props': {
13
+ label: 'grouped select',
14
+ fieldId: 'field-id',
15
+ groups: [
16
+ {
17
+ groupLabel: 'Ansible',
18
+ options: [
19
+ {
20
+ label: 'Ansible Roles - Ansible Default',
21
+ value: 168,
22
+ },
23
+ {
24
+ label: 'Ansible Roles - Install from git',
25
+ value: 170,
26
+ },
27
+ ],
28
+ },
29
+ ],
30
+ selected: 170,
31
+ setSelected: jest.fn(),
32
+ },
33
+ };
34
+
35
+ describe('GroupedSelectField', () => {
36
+ describe('rendering', () =>
37
+ testComponentSnapshotsWithFixtures(GroupedSelectField, fixtures));
38
+ });
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import * as patternfly from '@patternfly/react-core';
3
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
4
+ import { SelectField } from '../SelectField';
5
+
6
+ jest.spyOn(patternfly, 'Select');
7
+ jest.spyOn(patternfly, 'SelectOption');
8
+ patternfly.Select.mockImplementation(props => <div>{props}</div>);
9
+ patternfly.SelectOption.mockImplementation(props => <div>{props}</div>);
10
+ const fixtures = {
11
+ 'renders with props': {
12
+ label: 'grouped select',
13
+ fieldId: 'field-id',
14
+ options: ['Commands'],
15
+ value: 'Commands',
16
+ setValue: jest.fn(),
17
+ },
18
+ };
19
+
20
+ describe('SelectField', () => {
21
+ describe('rendering', () =>
22
+ testComponentSnapshotsWithFixtures(SelectField, fixtures));
23
+ });
@@ -0,0 +1,37 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`GroupedSelectField rendering renders with props 1`] = `
4
+ <FormGroup
5
+ fieldId="field-id"
6
+ label="grouped select"
7
+ >
8
+ <mockConstructor
9
+ className="without_select2"
10
+ isGrouped={true}
11
+ isOpen={false}
12
+ menuAppendTo={[Function]}
13
+ onClear={[Function]}
14
+ onFilter={[Function]}
15
+ onSelect={[Function]}
16
+ onToggle={[Function]}
17
+ selections={170}
18
+ variant="typeahead"
19
+ >
20
+ <SelectGroup
21
+ key="0"
22
+ label="Ansible"
23
+ >
24
+ <mockConstructor
25
+ key="0"
26
+ onClick={[Function]}
27
+ value="Ansible Roles - Ansible Default"
28
+ />
29
+ <mockConstructor
30
+ key="1"
31
+ onClick={[Function]}
32
+ value="Ansible Roles - Install from git"
33
+ />
34
+ </SelectGroup>
35
+ </mockConstructor>
36
+ </FormGroup>
37
+ `;
@@ -0,0 +1,23 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`SelectField rendering renders with props 1`] = `
4
+ <FormGroup
5
+ fieldId="field-id"
6
+ label="grouped select"
7
+ >
8
+ <mockConstructor
9
+ className="without_select2"
10
+ isOpen={false}
11
+ maxHeight="45vh"
12
+ menuAppendTo={[Function]}
13
+ onSelect={[Function]}
14
+ onToggle={[Function]}
15
+ selections="Commands"
16
+ >
17
+ <mockConstructor
18
+ key="0"
19
+ value="Commands"
20
+ />
21
+ </mockConstructor>
22
+ </FormGroup>
23
+ `;
@@ -0,0 +1 @@
1
+ export const foremanUrl = path => `foreman${path}`;
@@ -1,2 +1,21 @@
1
- export const selectAPIStatus = () => 'RESOLVED';
2
- export const selectAPIResponse = state => state;
1
+ export const selectAPI = state => state;
2
+ export const selectAPIByKey = (state, key) => selectAPI(state)[key] || {};
3
+
4
+ export const selectAPIStatus = (state, key) =>
5
+ selectAPIByKey(state, key).status;
6
+
7
+ export const selectAPIPayload = (state, key) =>
8
+ selectAPIByKey(state, key).payload || {};
9
+
10
+ export const selectAPIResponse = (state, key) =>
11
+ selectAPIByKey(state, key).response || {};
12
+
13
+ export const selectAPIError = (state, key) =>
14
+ selectAPIStatus(state, key) === 'ERROR'
15
+ ? selectAPIResponse(state, key)
16
+ : null;
17
+
18
+ export const selectAPIErrorMessage = (state, key) => {
19
+ const error = selectAPIError(state, key);
20
+ return error && error.message;
21
+ };
@@ -0,0 +1,5 @@
1
+ export const API = {
2
+ get: jest.fn(),
3
+ };
4
+
5
+ export const get = data => ({ type: 'get-some-type', ...data });
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ const PageLayout = ({ children }) => <div>{children}</div>;
5
+
6
+ PageLayout.propTypes = {
7
+ children: PropTypes.node.isRequired,
8
+ };
9
+
10
+ export default PageLayout;
@@ -1,4 +1,10 @@
1
1
  import { registerRoutes } from 'foremanReact/routes/RoutingService';
2
2
  import routes from './Routes/routes';
3
+ import fillregistrationAdvanced from './react_app/extend/fillregistrationAdvanced';
4
+ import fillRecentJobsCard from './react_app/extend/fillRecentJobsCard';
5
+ import registerReducers from './react_app/extend/reducers';
3
6
 
7
+ registerReducers();
4
8
  registerRoutes('foreman_remote_execution', routes);
9
+ fillRecentJobsCard();
10
+ fillregistrationAdvanced();
data/webpack/index.js CHANGED
@@ -1,8 +1,9 @@
1
- import { registerReducer } from 'foremanReact/common/MountingService';
2
1
  import componentRegistry from 'foremanReact/components/componentRegistry';
3
2
  import JobInvocationContainer from './react_app/components/jobInvocations';
4
3
  import TargetingHosts from './react_app/components/TargetingHosts';
5
- import rootReducer from './react_app/redux/reducers';
4
+ import registerReducers from './react_app/extend/reducers';
5
+
6
+ registerReducers();
6
7
 
7
8
  const components = [
8
9
  { name: 'JobInvocationContainer', type: JobInvocationContainer },
@@ -12,5 +13,3 @@ const components = [
12
13
  components.forEach(component => {
13
14
  componentRegistry.register(component);
14
15
  });
15
-
16
- registerReducer('foremanRemoteExecutionReducers', rootReducer);
@@ -0,0 +1,83 @@
1
+ /* eslint-disable camelcase */
2
+
3
+ import PropTypes from 'prop-types';
4
+ import React from 'react';
5
+ import Skeleton from 'react-loading-skeleton';
6
+ import ElipsisWithTooltip from 'react-ellipsis-with-tooltip';
7
+
8
+ import { Grid, GridItem } from '@patternfly/react-core';
9
+ import {
10
+ PropertiesSidePanel,
11
+ PropertyItem,
12
+ } from '@patternfly/react-catalog-view-extension';
13
+ import { ArrowIcon, ErrorCircleOIcon, OkIcon } from '@patternfly/react-icons';
14
+
15
+ import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
16
+ import CardItem from 'foremanReact/components/HostDetails/Templates/CardItem/CardTemplate';
17
+ import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
18
+ import { translate as __ } from 'foremanReact/common/I18n';
19
+ import './styles.css';
20
+
21
+ const RecentJobsCard = ({ hostDetails: { name } }) => {
22
+ const jobsUrl =
23
+ name && `/api/job_invocations?search=host%3D${name}&per_page=3`;
24
+ const {
25
+ response: { results: jobs },
26
+ } = useAPI('get', jobsUrl);
27
+
28
+ const iconMarkup = status => {
29
+ if (status === 1) return <ErrorCircleOIcon color="#C9190B" />;
30
+ return <OkIcon color="#3E8635" />;
31
+ };
32
+
33
+ return (
34
+ <CardItem
35
+ header={
36
+ <span>
37
+ {__('Recent Jobs')}{' '}
38
+ <a href={`/job_invocations?search=host+%3D+${name}`}>
39
+ <ArrowIcon />
40
+ </a>
41
+ </span>
42
+ }
43
+ >
44
+ <PropertiesSidePanel>
45
+ {jobs?.map(({ status, status_label, id, start_at, description }) => (
46
+ <PropertyItem
47
+ key={id}
48
+ label={
49
+ description ? (
50
+ <Grid>
51
+ <GridItem span={8}>
52
+ <ElipsisWithTooltip>{description}</ElipsisWithTooltip>
53
+ </GridItem>
54
+ <GridItem span={1}>{iconMarkup(status)}</GridItem>
55
+ <GridItem span={3}>{status_label}</GridItem>
56
+ </Grid>
57
+ ) : (
58
+ <Skeleton />
59
+ )
60
+ }
61
+ value={
62
+ start_at ? (
63
+ <a href={`/job_invocations/${id}`}>
64
+ <RelativeDateTime date={start_at} />
65
+ </a>
66
+ ) : (
67
+ <Skeleton />
68
+ )
69
+ }
70
+ />
71
+ ))}
72
+ </PropertiesSidePanel>
73
+ </CardItem>
74
+ );
75
+ };
76
+
77
+ export default RecentJobsCard;
78
+
79
+ RecentJobsCard.propTypes = {
80
+ hostDetails: PropTypes.shape({
81
+ name: PropTypes.string,
82
+ }).isRequired,
83
+ };
@@ -0,0 +1 @@
1
+ export const HOST_DETAILS_JOBS = 'HOST_DETAILS_JOBS';
@@ -0,0 +1 @@
1
+ export { default } from './RecentJobsCard';
@@ -0,0 +1,15 @@
1
+ .properties-side-panel-pf-property-value {
2
+ font-size: 14px !important;
3
+ margin-top: 8px;
4
+ word-break: break-word;
5
+ }
6
+
7
+ .properties-side-panel-pf-property-label {
8
+ font-weight: 700 !important;
9
+ font-size: 14px !important;
10
+ margin: 0 !important;
11
+ }
12
+
13
+ .properties-side-panel-pf-property {
14
+ margin-top: 24px;
15
+ }
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+
6
+ import { FormGroup, TextInput, Popover } from '@patternfly/react-core';
7
+
8
+ import { HelpIcon } from '@patternfly/react-icons';
9
+
10
+ const RexInterface = ({ isLoading, onChange }) => (
11
+ <FormGroup
12
+ label={__('Remote Execution Interface')}
13
+ fieldId="reg_rex_interface"
14
+ labelIcon={
15
+ <Popover
16
+ bodyContent={
17
+ <div>
18
+ {__('Identifier of the Host interface for Remote execution')}
19
+ </div>
20
+ }
21
+ >
22
+ <button
23
+ className="pf-c-form__group-label-help"
24
+ onClick={e => e.preventDefault()}
25
+ >
26
+ <HelpIcon noVerticalAlign />
27
+ </button>
28
+ </Popover>
29
+ }
30
+ >
31
+ <TextInput
32
+ type="text"
33
+ onBlur={e => onChange({ remoteExecutionInterface: e.target.value })}
34
+ id="reg_rex_interface_input"
35
+ isDisabled={isLoading}
36
+ />
37
+ </FormGroup>
38
+ );
39
+
40
+ RexInterface.propTypes = {
41
+ onChange: PropTypes.func,
42
+ isLoading: PropTypes.bool,
43
+ };
44
+
45
+ RexInterface.defaultProps = {
46
+ onChange: undefined,
47
+ isLoading: false,
48
+ };
49
+
50
+ export default RexInterface;
@@ -0,0 +1,9 @@
1
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
+ import RexInterface from '../RexInterface';
3
+
4
+ const fixtures = {
5
+ renders: { isLoading: false, onChange: () => {} },
6
+ };
7
+
8
+ describe('RexInterface', () =>
9
+ testComponentSnapshotsWithFixtures(RexInterface, fixtures));
@@ -0,0 +1,35 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`RexInterface renders 1`] = `
4
+ <FormGroup
5
+ fieldId="reg_rex_interface"
6
+ label="Remote Execution Interface"
7
+ labelIcon={
8
+ <Popover
9
+ bodyContent={
10
+ <div>
11
+ Identifier of the Host interface for Remote execution
12
+ </div>
13
+ }
14
+ >
15
+ <button
16
+ className="pf-c-form__group-label-help"
17
+ onClick={[Function]}
18
+ >
19
+ <HelpIcon
20
+ color="currentColor"
21
+ noVerticalAlign={true}
22
+ size="sm"
23
+ />
24
+ </button>
25
+ </Popover>
26
+ }
27
+ >
28
+ <TextInput
29
+ id="reg_rex_interface_input"
30
+ isDisabled={false}
31
+ onBlur={[Function]}
32
+ type="text"
33
+ />
34
+ </FormGroup>
35
+ `;