foreman_ansible 6.3.3 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/ansible_inventories_controller.rb +1 -1
  3. data/app/graphql/mutations/ansible_variable_overrides/create.rb +26 -0
  4. data/app/graphql/mutations/ansible_variable_overrides/delete.rb +38 -0
  5. data/app/graphql/mutations/ansible_variable_overrides/update.rb +26 -0
  6. data/app/graphql/mutations/hosts/assign_ansible_roles.rb +37 -0
  7. data/app/graphql/presenters/ansible_role_presenter.rb +12 -0
  8. data/app/graphql/presenters/overriden_ansible_variable_presenter.rb +19 -0
  9. data/app/graphql/types/ansible_role.rb +9 -0
  10. data/app/graphql/types/ansible_variable.rb +23 -0
  11. data/app/graphql/types/ansible_variable_override.rb +9 -0
  12. data/app/graphql/types/inherited_ansible_role.rb +13 -0
  13. data/app/graphql/types/overriden_ansible_variable.rb +27 -0
  14. data/app/helpers/foreman_ansible/ansible_reports_helper.rb +35 -54
  15. data/app/models/concerns/foreman_ansible/host_managed_extensions.rb +23 -4
  16. data/app/models/concerns/foreman_ansible/hostgroup_extensions.rb +1 -0
  17. data/app/models/foreman_ansible/ansible_provider.rb +56 -6
  18. data/app/services/foreman_ansible/ansible_report_importer.rb +2 -2
  19. data/app/services/foreman_ansible/inventory_creator.rb +1 -1
  20. data/app/services/foreman_ansible/override_resolver.rb +22 -0
  21. data/app/views/api/v2/ansible_override_values/index.json.rabl +3 -0
  22. data/app/views/api/v2/ansible_variables/show.json.rabl +1 -1
  23. data/app/views/foreman_ansible/ansible_roles/_hostgroup_ansible_roles_button.erb +3 -0
  24. data/app/views/foreman_ansible/config_reports/_ansible.html.erb +14 -5
  25. data/app/views/foreman_ansible/job_templates/ansible_roles_-_ansible_default.erb +4 -0
  26. data/app/views/foreman_ansible/job_templates/convert_to_rhel.erb +6 -2
  27. data/app/views/foreman_ansible/job_templates/run_openscap_scans_-_ansible_default.erb +20 -0
  28. data/config/routes.rb +3 -0
  29. data/db/migrate/20210818083407_fix_ansible_setting_category_to_dsl.rb +5 -0
  30. data/lib/foreman_ansible/engine.rb +0 -18
  31. data/lib/foreman_ansible/register.rb +114 -2
  32. data/lib/foreman_ansible/version.rb +1 -1
  33. data/package.json +10 -6
  34. data/test/functional/api/v2/ansible_inventories_controller_test.rb +1 -2
  35. data/test/graphql/mutations/hosts/assign_ansible_roles_mutation_test.rb +96 -0
  36. data/test/graphql/queries/ansible_roles_query_test.rb +35 -0
  37. data/test/unit/ansible_provider_test.rb +3 -6
  38. data/test/unit/concerns/host_managed_extensions_test.rb +8 -0
  39. data/test/unit/concerns/hostgroup_extensions_test.rb +6 -0
  40. data/test/unit/helpers/ansible_reports_helper_test.rb +4 -30
  41. data/test/unit/services/override_resolver_test.rb +34 -0
  42. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.js +59 -0
  43. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.scss +6 -0
  44. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.test.js +20 -0
  45. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.js +22 -0
  46. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.scss +4 -0
  47. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.test.js +104 -0
  48. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/index.js +38 -0
  49. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverrides.scss +3 -0
  50. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTable.js +238 -0
  51. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTableHelper.js +111 -0
  52. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.js +161 -0
  53. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.scss +7 -0
  54. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableActionHelper.js +49 -0
  55. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValue.js +70 -0
  56. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValueHelper.js +35 -0
  57. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.fixtures.js +429 -0
  58. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.test.js +71 -0
  59. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesDelete.test.js +74 -0
  60. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesUpdate.test.js +188 -0
  61. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/index.js +58 -0
  62. data/webpack/components/AnsibleHostDetail/components/JobsTab/JobsTabHelper.js +79 -0
  63. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobHelper.js +106 -0
  64. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.js +129 -0
  65. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.scss +7 -0
  66. data/webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js +103 -0
  67. data/webpack/components/AnsibleHostDetail/components/JobsTab/RecurringJobsTable.js +96 -0
  68. data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.fixtures.js +184 -0
  69. data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.test.js +195 -0
  70. data/webpack/components/AnsibleHostDetail/components/JobsTab/index.js +88 -0
  71. data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/AllRolesTable.js +89 -0
  72. data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/index.js +80 -0
  73. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesForm.js +90 -0
  74. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModal.scss +3 -0
  75. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModalHelper.js +40 -0
  76. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/index.js +82 -0
  77. data/webpack/components/AnsibleHostDetail/components/RolesTab/RolesTable.js +129 -0
  78. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/EditRoles.test.js +85 -0
  79. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.fixtures.js +180 -0
  80. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.test.js +75 -0
  81. data/webpack/components/AnsibleHostDetail/components/RolesTab/index.js +51 -0
  82. data/webpack/components/AnsibleHostDetail/components/SecondaryTabRoutes.js +60 -0
  83. data/webpack/components/AnsibleHostDetail/components/TabLayout.js +12 -0
  84. data/webpack/components/AnsibleHostDetail/constants.js +9 -0
  85. data/webpack/components/AnsibleHostDetail/helpers.js +4 -0
  86. data/webpack/components/AnsibleHostDetail/index.js +6 -0
  87. data/webpack/components/AnsibleRolesAndVariables/__test__/AnsibleRolesAndVariablesImport.test.js +15 -10
  88. data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.js +29 -0
  89. data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.test.js +3 -0
  90. data/webpack/components/AnsibleRolesSwitcher/components/AvailableRolesList.js +2 -1
  91. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AnsiblePermissionDenied.test.js.snap +2 -0
  92. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AnsibleRole.test.js.snap +3 -3
  93. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AssignedRolesList.test.js.snap +4 -4
  94. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AvailableRolesList.test.js.snap +9 -0
  95. data/webpack/components/DualList/DualList.scss +3 -0
  96. data/webpack/components/DualList/ListControls.js +65 -0
  97. data/webpack/components/DualList/ListHeader.js +16 -0
  98. data/webpack/components/DualList/ListItem.js +69 -0
  99. data/webpack/components/DualList/ListPane.js +95 -0
  100. data/webpack/components/DualList/SelectedStatus.js +21 -0
  101. data/webpack/components/DualList/index.js +103 -0
  102. data/webpack/components/ErrorState.js +16 -0
  103. data/webpack/components/withLoading.js +135 -0
  104. data/webpack/components/withPagination.js +0 -0
  105. data/webpack/formHelper.js +131 -0
  106. data/webpack/globalIdHelper.js +13 -0
  107. data/webpack/global_index.js +18 -0
  108. data/webpack/graphql/mutations/assignAnsibleRoles.gql +17 -0
  109. data/webpack/graphql/mutations/cancelRecurringLogic.gql +12 -0
  110. data/webpack/graphql/mutations/createAnsibleVariableOverride.gql +28 -0
  111. data/webpack/graphql/mutations/createJobInvocation.gql +11 -0
  112. data/webpack/graphql/mutations/deleteAnsibleVariableOverride.gql +17 -0
  113. data/webpack/graphql/mutations/updateAnsibleVariableOverride.gql +29 -0
  114. data/webpack/graphql/queries/allAnsibleRoles.gql +13 -0
  115. data/webpack/graphql/queries/ansibleRoles.gql +13 -0
  116. data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
  117. data/webpack/graphql/queries/hostAnsibleRoles.gql +17 -0
  118. data/webpack/graphql/queries/hostAvailableAnsibleRoles.gql +11 -0
  119. data/webpack/graphql/queries/hostVariableOverrides.gql +39 -0
  120. data/webpack/graphql/queries/recurringJobs.gql +28 -0
  121. data/webpack/helpers/pageParamsHelper.js +40 -0
  122. data/webpack/helpers/paginationHelper.js +9 -0
  123. data/webpack/permissionsHelper.js +58 -0
  124. data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.fixtures.js +63 -0
  125. data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.test.js +112 -0
  126. data/webpack/routes/HostgroupJobs/index.js +26 -0
  127. data/webpack/routes/routes.js +10 -0
  128. data/webpack/testHelper.js +165 -0
  129. data/webpack/toastHelper.js +4 -0
  130. metadata +130 -78
  131. data/app/assets/images/foreman_ansible/Ansible.png +0 -0
  132. data/app/models/foreman_ansible/fact_name.rb +0 -16
  133. data/app/models/setting/ansible.rb +0 -106
  134. data/app/services/foreman_ansible/fact_importer.rb +0 -99
  135. data/app/services/foreman_ansible/fact_parser.rb +0 -126
  136. data/app/services/foreman_ansible/fact_sparser.rb +0 -37
  137. data/app/services/foreman_ansible/operating_system_parser.rb +0 -102
  138. data/app/services/foreman_ansible/structured_fact_importer.rb +0 -25
  139. data/test/unit/lib/foreman_ansible_core/ansible_runner_test.rb +0 -51
  140. data/test/unit/lib/foreman_ansible_core/command_creator_test.rb +0 -64
  141. data/test/unit/lib/foreman_ansible_core/playbook_runner_test.rb +0 -110
  142. data/test/unit/services/fact_importer_test.rb +0 -52
  143. data/test/unit/services/fact_parser_test.rb +0 -281
  144. data/test/unit/services/fact_sparser_test.rb +0 -24
  145. data/test/unit/services/structured_fact_importer_test.rb +0 -30
  146. data/webpack/__mocks__/foremanReact/common/I18n.js +0 -1
  147. data/webpack/__mocks__/foremanReact/common/helpers.js +0 -13
  148. data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +0 -2
  149. data/webpack/__mocks__/foremanReact/components/common/EmptyState.js +0 -5
  150. data/webpack/__mocks__/foremanReact/components/common/forms/OrderableSelect/helpers.js +0 -5
  151. data/webpack/__mocks__/foremanReact/redux/API.js +0 -7
  152. data/webpack/components/AnsibleRolesAndVariables/__test__/__snapshots__/AnsibleRolesAndVariablesImport.test.js.snap +0 -177
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Tabs, Tab, TabTitleText } from '@patternfly/react-core';
4
+ import { useHistory } from 'react-router-dom';
5
+ import SkeletonLoader from 'foremanReact/components/common/SkeletonLoader';
6
+
7
+ import SecondaryTabRoutes from './components/SecondaryTabRoutes';
8
+ import { SECONDARY_TABS } from './constants';
9
+ import './AnsibleHostDetail.scss';
10
+
11
+ const AnsibleHostDetail = ({
12
+ response,
13
+ status,
14
+ router,
15
+ location: { pathname },
16
+ history,
17
+ }) => {
18
+ const hashHistory = useHistory();
19
+ return (
20
+ <SkeletonLoader status={status} skeletonProps={{ count: 5 }}>
21
+ {response?.id && (
22
+ <>
23
+ <Tabs
24
+ onSelect={(evt, subTab) => hashHistory.push(subTab)}
25
+ activeKey={pathname?.split('/')[2]}
26
+ isSecondary
27
+ >
28
+ {SECONDARY_TABS.map(({ key, title }) => (
29
+ <Tab
30
+ key={key}
31
+ eventKey={key}
32
+ title={<TabTitleText>{title}</TabTitleText>}
33
+ />
34
+ ))}
35
+ </Tabs>
36
+ <SecondaryTabRoutes
37
+ response={response}
38
+ router={router}
39
+ history={history}
40
+ />
41
+ </>
42
+ )}
43
+ </SkeletonLoader>
44
+ );
45
+ };
46
+
47
+ AnsibleHostDetail.propTypes = {
48
+ response: PropTypes.object.isRequired,
49
+ status: PropTypes.string.isRequired,
50
+ location: PropTypes.object,
51
+ router: PropTypes.object.isRequired,
52
+ history: PropTypes.object.isRequired,
53
+ };
54
+
55
+ AnsibleHostDetail.defaultProps = {
56
+ location: { pathname: '' },
57
+ };
58
+
59
+ export default AnsibleHostDetail;
@@ -0,0 +1,6 @@
1
+ @import '~@theforeman/vendor/scss/variables';
2
+
3
+ .ansible-host-detail {
4
+ background-color: $pf-color-white;
5
+ padding: 1.8rem;
6
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import AnsibleHostDetail from './';
5
+
6
+ describe('AnsibleHostDetail', () => {
7
+ it('should show skeleton when loading', () => {
8
+ const { container } = render(
9
+ <AnsibleHostDetail
10
+ status="PENDING"
11
+ response={{ id: 5, name: 'test.example.com' }}
12
+ router={{}}
13
+ history={{}}
14
+ />
15
+ );
16
+ expect(
17
+ container.getElementsByClassName('react-loading-skeleton')
18
+ ).toHaveLength(5);
19
+ });
20
+ });
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import Immutable from 'seamless-immutable';
3
+ import PropTypes from 'prop-types';
4
+ import './AnsibleHostInventory.scss';
5
+ import ReportJsonViewer from '../../../ReportJsonViewer';
6
+
7
+ const AnsibleHostInventory = ({ inventoryData }) => {
8
+ const mutableInventory =
9
+ inventoryData && Immutable.asMutable(inventoryData, { deep: true });
10
+
11
+ return (
12
+ <div className="ansible-host-inventory">
13
+ <ReportJsonViewer data={mutableInventory} />
14
+ </div>
15
+ );
16
+ };
17
+
18
+ AnsibleHostInventory.propTypes = {
19
+ inventoryData: PropTypes.object.isRequired,
20
+ };
21
+
22
+ export default AnsibleHostInventory;
@@ -0,0 +1,4 @@
1
+
2
+ .ansible-host-inventory {
3
+ padding: 1.8rem;
4
+ }
@@ -0,0 +1,104 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import AnsibleHostInventory from './AnsibleHostInventory';
5
+
6
+ const inventoryData = {
7
+ all: {
8
+ hosts: ['jim-dorsay.tlv.redhat.com'],
9
+ vars: {},
10
+ },
11
+ _meta: {
12
+ hostvars: {
13
+ 'jim-dorsay.tlv.redhat.com': {
14
+ foreman: {
15
+ hostname: 'jim-dorsay',
16
+ fqdn: 'jim-dorsay.tlv.redhat.com',
17
+ hostgroup: 'libvirt',
18
+ foreman_subnets: [
19
+ {
20
+ name: 'subnet',
21
+ network: '192.168.122.0',
22
+ mask: '255.255.255.0',
23
+ gateway: '',
24
+ dns_primary: '',
25
+ dns_secondary: '',
26
+ from: '',
27
+ to: '',
28
+ boot_mode: 'DHCP',
29
+ ipam: 'DHCP',
30
+ vlanid: null,
31
+ mtu: 1500,
32
+ nic_delay: null,
33
+ network_type: 'IPv4',
34
+ description: '',
35
+ },
36
+ ],
37
+ foreman_interfaces: [
38
+ {
39
+ ip: '192.168.122.139',
40
+ ip6: '',
41
+ mac: '52:54:00:bd:01:0d',
42
+ name: 'jim-dorsay.tlv.redhat.com',
43
+ attrs: {},
44
+ virtual: false,
45
+ link: true,
46
+ identifier: '',
47
+ managed: true,
48
+ primary: true,
49
+ provision: true,
50
+ subnet: {},
51
+ subnet6: null,
52
+ tag: null,
53
+ attached_to: null,
54
+ type: 'Interface',
55
+ },
56
+ ],
57
+ location: 'Default Location',
58
+ location_title: 'Default Location',
59
+ organization: 'Default Organization',
60
+ organization_title: 'Default Organization',
61
+ domainname: 'tlv.redhat.com',
62
+ foreman_domain_description: '',
63
+ owner_name: 'Admin User',
64
+ owner_email: 'root@localdomain',
65
+ ssh_authorized_keys: [],
66
+ foreman_users: {
67
+ admin: {
68
+ firstname: 'Admin',
69
+ lastname: 'User',
70
+ mail: 'root@localdomain',
71
+ description: '',
72
+ fullname: 'Admin User',
73
+ name: 'admin',
74
+ ssh_authorized_keys: [],
75
+ },
76
+ },
77
+ root_pw:
78
+ '$5$I6cmWY8Uy4NaTfqW$wkKsQzinnd2iNTHvYPYPxO/YkHlvT/sgVF1n6paZvd8',
79
+ foreman_config_groups: [],
80
+ puppetmaster: '',
81
+ },
82
+ foreman_ansible_roles: ['foreman_ansible_test'],
83
+ ansible_roles_check_mode: false,
84
+ host_packages: '',
85
+ host_registration_insights: false,
86
+ host_registration_remote_execution: true,
87
+ remote_execution_ssh_keys: [
88
+ 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC6ERZ9H22Di8mHRcxoTyIc+kcKCpGYq6G4/Ub9WK+DZlZZNMXCcO0WydV9GyJX9eIGrStDxZvxtJQQoOvYRS8d92RZPy3wIR4GtingmVNpwehNePHAcF1Aj9nqm+A4V49R3PqC28ctzBaJAbrvnNh/xDCsTW/ogexu8yN4iKkt0ZijUTnAZ2w1dXit23iITm0I8NPYqxRFNQOYtLYBZDKX+rPanjkyYGEyvi/RkhhHOimMjhAR0Qo9Vqm438LZWrZzGAffiE3AVYBWd3Eh2B5nW1q4cmS7CYOfsQCmO9u9x9lFzZCfdIFVWBhSyqR7cw/M7rTmixUNoX3QvSM0A+z/pBx++SgB+LhV2Zmek68NvcG/9LElD3pVESyvqtRbQQtb7Y6e553QUBn4lOg/N/p67TzfySCm0QhU5dKyj52Bvg6yhrwvqbVw1lwpjf6CNLERcRsTomIUDLVzDdcxo+u5GZ2J/hi5xH7NNMT89oPErwChm8wUDQyAdKGntCFP4D8= yifatfanimakias@localhost.localdomain',
89
+ ],
90
+ remote_execution_ssh_user: 'root',
91
+ remote_execution_effective_user_method: 'sudo',
92
+ remote_execution_connect_by_ip: false,
93
+ },
94
+ },
95
+ },
96
+ };
97
+
98
+ describe('AnsibleHostInventory', () => {
99
+ it('should show inventory', () => {
100
+ render(<AnsibleHostInventory inventoryData={inventoryData} />);
101
+ expect(screen.getByText('_meta:')).toBeInTheDocument();
102
+ expect(screen.getByText('all:')).toBeInTheDocument();
103
+ });
104
+ });
@@ -0,0 +1,38 @@
1
+ import React, { useMemo } from 'react';
2
+ import { foremanUrl } from 'foremanReact/common/helpers';
3
+ import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
4
+ import Skeleton from 'react-loading-skeleton';
5
+ import { STATUS } from 'foremanReact/constants';
6
+
7
+ import PropTypes from 'prop-types';
8
+ import AnsibleHostInventory from './AnsibleHostInventory';
9
+ import ErrorState from '../../../ErrorState';
10
+
11
+ const WrappedAnsibleHostInventory = ({ hostId }) => {
12
+ const params = useMemo(() => ({ params: { host_ids: [hostId] } }), [hostId]);
13
+
14
+ const url = hostId && foremanUrl('/ansible/api/ansible_inventories/hosts');
15
+ const { response: inventory, status } = useAPI('get', url, params);
16
+
17
+ if (status === STATUS.PENDING) {
18
+ return <Skeleton count={5} />;
19
+ }
20
+
21
+ if (status === STATUS.ERROR) {
22
+ return (
23
+ <ErrorState description={inventory?.response?.data?.error?.message} />
24
+ );
25
+ }
26
+
27
+ return <AnsibleHostInventory inventoryData={inventory} />;
28
+ };
29
+
30
+ WrappedAnsibleHostInventory.propTypes = {
31
+ hostId: PropTypes.number,
32
+ };
33
+
34
+ WrappedAnsibleHostInventory.defaultProps = {
35
+ hostId: undefined,
36
+ };
37
+
38
+ export default WrappedAnsibleHostInventory;
@@ -0,0 +1,3 @@
1
+ .ansible-tab-margin {
2
+ margin: 1.8rem 1rem;
3
+ }
@@ -0,0 +1,238 @@
1
+ import React, { useReducer } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useDispatch } from 'react-redux';
4
+ import { useMutation } from '@apollo/client';
5
+
6
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
7
+ import { usePaginationOptions } from 'foremanReact/components/Pagination/PaginationHooks';
8
+ import { openConfirmModal } from 'foremanReact/components/ConfirmModal';
9
+ import {
10
+ TableComposable,
11
+ Thead,
12
+ Tbody,
13
+ Tr,
14
+ Th,
15
+ Td,
16
+ } from '@patternfly/react-table';
17
+ import { Flex, FlexItem, Pagination } from '@patternfly/react-core';
18
+
19
+ import deleteAnsibleVariableOverride from '../../../../graphql/mutations/deleteAnsibleVariableOverride.gql';
20
+ import EditableAction from './EditableAction';
21
+ import EditableValue from './EditableValue';
22
+ import { decodeModelId } from '../../../../globalIdHelper';
23
+ import {
24
+ formatSourceAttr,
25
+ findOverride,
26
+ validateValue,
27
+ onCompleted,
28
+ onError,
29
+ } from './AnsibleVariableOverridesTableHelper';
30
+
31
+ import withLoading from '../../../withLoading';
32
+ import {
33
+ preparePerPageOptions,
34
+ refreshPage,
35
+ } from '../../../../helpers/paginationHelper';
36
+
37
+ const reducer = (state, action) =>
38
+ state.map((item, index) => {
39
+ if (action.idx === index) {
40
+ return { ...item, ...action.payload };
41
+ }
42
+ return item;
43
+ });
44
+
45
+ const initState = vars =>
46
+ vars.map((variable, idx) => ({
47
+ open: false,
48
+ value: variable.currentValue
49
+ ? variable.currentValue.value
50
+ : variable.defaultValue,
51
+ validation: { key: 'noval', msg: '' },
52
+ working: false,
53
+ }));
54
+
55
+ const AnsibleVariableOverridesTable = ({
56
+ variables,
57
+ hostAttrs,
58
+ hostId,
59
+ hostGlobalId,
60
+ totalCount,
61
+ pagination,
62
+ history,
63
+ }) => {
64
+ const columns = [
65
+ __('Name'),
66
+ __('Ansible Role'),
67
+ __('Type'),
68
+ __('Value'),
69
+ __('Source attribute'),
70
+ ];
71
+
72
+ const handlePerPageSelected = (event, perPage) => {
73
+ refreshPage(history, { page: 1, perPage });
74
+ };
75
+
76
+ const handlePageSelected = (event, page) => {
77
+ refreshPage(history, { ...pagination, page });
78
+ };
79
+
80
+ const perPageOptions = preparePerPageOptions(usePaginationOptions());
81
+
82
+ const [editableState, innerDispatch] = useReducer(
83
+ reducer,
84
+ variables,
85
+ initState
86
+ );
87
+
88
+ const toggleWorking = idx => flag => {
89
+ innerDispatch({ idx, payload: { working: flag } });
90
+ };
91
+
92
+ const setEditable = (idx, flag) => () => {
93
+ innerDispatch({ idx, payload: { open: flag } });
94
+ };
95
+
96
+ const onValueChange = (idx, variable) => value => {
97
+ const payload = {
98
+ value,
99
+ validation: validateValue(variable, value),
100
+ };
101
+ innerDispatch({ idx, payload });
102
+ };
103
+
104
+ const onSubmitSuccess = (idx, variable) => newValue => {
105
+ innerDispatch({
106
+ idx,
107
+ payload: {
108
+ open: false,
109
+ working: false,
110
+ value: newValue,
111
+ validation: validateValue(variable, newValue),
112
+ },
113
+ });
114
+ };
115
+
116
+ const onValidationError = idx => error => {
117
+ innerDispatch({
118
+ idx,
119
+ payload: {
120
+ working: false,
121
+ validation: { key: 'error', msg: error },
122
+ },
123
+ });
124
+ };
125
+
126
+ const dispatch = useDispatch();
127
+ const [callMutation] = useMutation(deleteAnsibleVariableOverride);
128
+
129
+ const deleteAction = (variable, idx) => ({
130
+ title: __('Delete'),
131
+ onClick: () => {
132
+ dispatch(
133
+ openConfirmModal({
134
+ title: __('Delete Ansible Variable Override'),
135
+ message:
136
+ variable &&
137
+ sprintf(
138
+ __('Are you sure you want to delete override for %s?'),
139
+ variable.key
140
+ ),
141
+ onConfirm: () => {
142
+ callMutation({
143
+ variables: {
144
+ id: findOverride(variable, hostAttrs.name).id,
145
+ hostId,
146
+ variableId: decodeModelId(variable),
147
+ },
148
+ }).then(onCompleted(onValueChange(idx, variable)), onError); // eslint-disable-line
149
+ },
150
+ })
151
+ );
152
+ },
153
+ });
154
+
155
+ const actionsResolver = (variable, idx) => {
156
+ const actions = [];
157
+ if (variable.currentValue?.element === 'fqdn' && variable.meta.canEdit) {
158
+ actions.push(deleteAction(variable, idx));
159
+ }
160
+ return actions;
161
+ };
162
+
163
+ return (
164
+ <React.Fragment>
165
+ <Flex>
166
+ <FlexItem align={{ default: 'alignRight' }}>
167
+ <Pagination
168
+ itemCount={totalCount}
169
+ page={pagination.page}
170
+ perPage={pagination.perPage}
171
+ onSetPage={handlePageSelected}
172
+ onPerPageSelect={handlePerPageSelected}
173
+ perPageOptions={perPageOptions}
174
+ variant="top"
175
+ />
176
+ </FlexItem>
177
+ </Flex>
178
+ <TableComposable variant="compact">
179
+ <Thead>
180
+ <Tr>
181
+ {columns.map(col => (
182
+ <Th key={col}>{col}</Th>
183
+ ))}
184
+ <Th />
185
+ </Tr>
186
+ </Thead>
187
+ <Tbody>
188
+ {variables.map((variable, idx) => (
189
+ <Tr key={idx}>
190
+ <Td>{variable.key}</Td>
191
+ <Td>{variable.ansibleRoleName}</Td>
192
+ <Td>{variable.parameterType}</Td>
193
+ <Td>
194
+ <EditableValue
195
+ variable={variable}
196
+ editing={editableState[idx].open}
197
+ onChange={onValueChange(idx, variable)}
198
+ value={editableState[idx].value}
199
+ validation={editableState[idx].validation}
200
+ working={editableState[idx].working}
201
+ />
202
+ </Td>
203
+ <Td>{formatSourceAttr(variable)}</Td>
204
+ <Td>
205
+ <EditableAction
206
+ open={editableState[idx].open}
207
+ onClose={setEditable(idx, false)}
208
+ onOpen={setEditable(idx, true)}
209
+ toggleWorking={toggleWorking(idx)}
210
+ variable={variable}
211
+ state={editableState[idx]}
212
+ hostId={hostId}
213
+ hostName={hostAttrs.name}
214
+ hostGlobalId={hostGlobalId}
215
+ onSubmitSuccess={onSubmitSuccess(idx, variable)}
216
+ onValidationError={onValidationError(idx)}
217
+ />
218
+ </Td>
219
+ <Td actions={{ items: actionsResolver(variable, idx) }} />
220
+ </Tr>
221
+ ))}
222
+ </Tbody>
223
+ </TableComposable>
224
+ </React.Fragment>
225
+ );
226
+ };
227
+
228
+ AnsibleVariableOverridesTable.propTypes = {
229
+ variables: PropTypes.array.isRequired,
230
+ hostAttrs: PropTypes.object.isRequired,
231
+ hostId: PropTypes.number.isRequired,
232
+ hostGlobalId: PropTypes.string.isRequired,
233
+ totalCount: PropTypes.number.isRequired,
234
+ pagination: PropTypes.object.isRequired,
235
+ history: PropTypes.object.isRequired,
236
+ };
237
+
238
+ export default withLoading(AnsibleVariableOverridesTable);
@@ -0,0 +1,111 @@
1
+ import React from 'react';
2
+ import { TimesIcon, CheckIcon } from '@patternfly/react-icons';
3
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ import { showToast } from '../../../../toastHelper';
6
+
7
+ const formatSourceLink = currentValue =>
8
+ `${__(currentValue.element)}: ${currentValue.elementName}`;
9
+
10
+ export const formatSourceAttr = variable =>
11
+ variable.currentValue
12
+ ? formatSourceLink(variable.currentValue)
13
+ : __('Default value');
14
+
15
+ export const formatValue = variable => {
16
+ const value = variable.currentValue
17
+ ? variable.currentValue.value
18
+ : variable.defaultValue;
19
+
20
+ switch (variable.parameterType) {
21
+ case 'boolean':
22
+ return value ? <CheckIcon /> : <TimesIcon />;
23
+ case 'yaml':
24
+ case 'hash':
25
+ case 'array':
26
+ return JSON.stringify(value);
27
+ default:
28
+ return value;
29
+ }
30
+ };
31
+
32
+ const joinErrors = errors => errors.map(err => err.message).join(', ');
33
+
34
+ export const onCompleted = onSubmitSuccess => response => {
35
+ const {
36
+ errors,
37
+ overridenAnsibleVariable,
38
+ } = response.data.deleteAnsibleVariableOverride;
39
+ if (Array.isArray(errors) && errors.length > 0) {
40
+ showToast({
41
+ type: 'error',
42
+ message: formatError(joinErrors(errors)),
43
+ });
44
+ } else {
45
+ onSubmitSuccess(overridenAnsibleVariable.currentValue.value);
46
+ showToast({
47
+ type: 'success',
48
+ message: __('Ansible variable override was successfully deleted.'),
49
+ });
50
+ }
51
+ };
52
+
53
+ export const findOverride = (variable, hostname) =>
54
+ variable.lookupValues.nodes.find(
55
+ item =>
56
+ item.value === variable.currentValue.value &&
57
+ item.match === `fqdn=${hostname}`
58
+ );
59
+
60
+ const formatError = error =>
61
+ sprintf(
62
+ __(
63
+ 'There was a following error when deleting Ansible variable override: %s'
64
+ ),
65
+ error
66
+ );
67
+
68
+ export const onError = response => {
69
+ showToast({ type: 'error', message: formatError(response.error) });
70
+ };
71
+
72
+ const validationSuccess = { key: 'success', msg: '' };
73
+
74
+ const validateRegexp = (variable, value) => {
75
+ if (new RegExp(variable.validatorRule).test(value)) {
76
+ return validationSuccess;
77
+ }
78
+ return {
79
+ key: 'error',
80
+ msg: sprintf(
81
+ __('Invalid, expected to match a regex: %s'),
82
+ variable.validatorRule
83
+ ),
84
+ };
85
+ };
86
+
87
+ const validateList = (variable, value) => {
88
+ if (variable.validatorRule.split(',').find(item => item.trim() === value)) {
89
+ return validationSuccess;
90
+ }
91
+ return {
92
+ key: 'error',
93
+ msg: sprintf(__('Invalid, expected one of: %s'), variable.validatorRule),
94
+ };
95
+ };
96
+
97
+ export const validateValue = (variable, value) => {
98
+ if (variable.required && !value) {
99
+ return { key: 'error', msg: __('is required') };
100
+ }
101
+
102
+ if (variable.validatorType === 'regexp') {
103
+ return validateRegexp(variable, value);
104
+ }
105
+
106
+ if (variable.validatorType === 'list') {
107
+ return validateList(variable, value);
108
+ }
109
+
110
+ return { key: 'noval', msg: '' };
111
+ };