foreman_ansible 6.4.1 → 7.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/ansible_inventories_controller.rb +1 -1
- data/app/graphql/mutations/ansible_variable_overrides/create.rb +26 -0
- data/app/graphql/mutations/ansible_variable_overrides/delete.rb +38 -0
- data/app/graphql/mutations/ansible_variable_overrides/update.rb +26 -0
- data/app/graphql/mutations/hosts/assign_ansible_roles.rb +37 -0
- data/app/graphql/presenters/ansible_role_presenter.rb +12 -0
- data/app/graphql/presenters/overriden_ansible_variable_presenter.rb +19 -0
- data/app/graphql/types/ansible_role.rb +9 -0
- data/app/graphql/types/ansible_variable.rb +23 -0
- data/app/graphql/types/ansible_variable_override.rb +9 -0
- data/app/graphql/types/inherited_ansible_role.rb +13 -0
- data/app/graphql/types/overriden_ansible_variable.rb +27 -0
- data/app/helpers/foreman_ansible/ansible_roles_data_preparations.rb +22 -22
- data/app/models/concerns/foreman_ansible/host_managed_extensions.rb +23 -4
- data/app/models/concerns/foreman_ansible/hostgroup_extensions.rb +2 -1
- data/app/models/foreman_ansible/ansible_provider.rb +7 -5
- data/app/services/foreman_ansible/ansible_report_importer.rb +2 -2
- data/app/services/foreman_ansible/inventory_creator.rb +1 -1
- data/app/services/foreman_ansible/override_resolver.rb +22 -0
- data/app/views/api/v2/ansible_override_values/index.json.rabl +3 -0
- data/app/views/api/v2/ansible_variables/show.json.rabl +1 -1
- data/app/views/foreman_ansible/ansible_roles/_hostgroup_ansible_roles_button.erb +3 -0
- data/app/views/foreman_ansible/job_templates/convert_to_rhel.erb +6 -2
- data/app/views/foreman_ansible/job_templates/run_openscap_scans_-_ansible_default.erb +20 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20210818083407_fix_ansible_setting_category_to_dsl.rb +5 -0
- data/lib/foreman_ansible/engine.rb +0 -17
- data/lib/foreman_ansible/register.rb +115 -4
- data/lib/foreman_ansible/version.rb +1 -1
- data/package.json +4 -2
- data/test/functional/api/v2/ansible_inventories_controller_test.rb +1 -2
- data/test/graphql/mutations/hosts/assign_ansible_roles_mutation_test.rb +96 -0
- data/test/graphql/queries/ansible_roles_query_test.rb +35 -0
- data/test/unit/ansible_provider_test.rb +3 -6
- data/test/unit/concerns/host_managed_extensions_test.rb +8 -0
- data/test/unit/concerns/hostgroup_extensions_test.rb +6 -0
- data/test/unit/hostgroup_ansible_role_test.rb +13 -0
- data/test/unit/services/override_resolver_test.rb +34 -0
- data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.js +51 -27
- data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.test.js +12 -6
- data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.js +22 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.scss +4 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.test.js +104 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/index.js +38 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverrides.scss +3 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTable.js +238 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTableHelper.js +111 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.js +161 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.scss +7 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableActionHelper.js +49 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValue.js +70 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValueHelper.js +35 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.fixtures.js +429 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.test.js +71 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesDelete.test.js +74 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesUpdate.test.js +188 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/index.js +58 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/JobsTabHelper.js +79 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobHelper.js +106 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.js +129 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.scss +7 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js +103 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/RecurringJobsTable.js +96 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.fixtures.js +184 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.test.js +195 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/index.js +88 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/AllRolesTable.js +89 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/index.js +80 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesForm.js +90 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModal.scss +3 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModalHelper.js +40 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/index.js +82 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/RolesTable.js +129 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/EditRoles.test.js +85 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.fixtures.js +180 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.test.js +75 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/index.js +51 -0
- data/webpack/components/AnsibleHostDetail/components/SecondaryTabRoutes.js +60 -0
- data/webpack/components/AnsibleHostDetail/components/TabLayout.js +12 -0
- data/webpack/components/AnsibleHostDetail/constants.js +9 -0
- data/webpack/components/AnsibleHostDetail/helpers.js +4 -0
- data/webpack/components/AnsibleRolesAndVariables/__test__/AnsibleRolesAndVariablesImport.test.js +15 -10
- data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.js +29 -0
- data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.test.js +3 -0
- data/webpack/components/AnsibleRolesSwitcher/components/AvailableRolesList.js +2 -1
- data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AnsibleRole.test.js.snap +3 -3
- data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AvailableRolesList.test.js.snap +4 -0
- data/webpack/components/DualList/DualList.scss +3 -0
- data/webpack/components/DualList/ListControls.js +65 -0
- data/webpack/components/DualList/ListHeader.js +16 -0
- data/webpack/components/DualList/ListItem.js +69 -0
- data/webpack/components/DualList/ListPane.js +95 -0
- data/webpack/components/DualList/SelectedStatus.js +21 -0
- data/webpack/components/DualList/index.js +103 -0
- data/webpack/components/ErrorState.js +16 -0
- data/webpack/components/withLoading.js +135 -0
- data/webpack/components/withPagination.js +0 -0
- data/webpack/formHelper.js +131 -0
- data/webpack/globalIdHelper.js +13 -0
- data/webpack/global_index.js +7 -1
- data/webpack/graphql/mutations/assignAnsibleRoles.gql +17 -0
- data/webpack/graphql/mutations/cancelRecurringLogic.gql +12 -0
- data/webpack/graphql/mutations/createAnsibleVariableOverride.gql +28 -0
- data/webpack/graphql/mutations/createJobInvocation.gql +11 -0
- data/webpack/graphql/mutations/deleteAnsibleVariableOverride.gql +17 -0
- data/webpack/graphql/mutations/updateAnsibleVariableOverride.gql +29 -0
- data/webpack/graphql/queries/allAnsibleRoles.gql +13 -0
- data/webpack/graphql/queries/ansibleRoles.gql +13 -0
- data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
- data/webpack/graphql/queries/hostAnsibleRoles.gql +17 -0
- data/webpack/graphql/queries/hostAvailableAnsibleRoles.gql +11 -0
- data/webpack/graphql/queries/hostVariableOverrides.gql +39 -0
- data/webpack/graphql/queries/recurringJobs.gql +28 -0
- data/webpack/helpers/pageParamsHelper.js +40 -0
- data/webpack/helpers/paginationHelper.js +9 -0
- data/webpack/permissionsHelper.js +58 -0
- data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.fixtures.js +63 -0
- data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.test.js +112 -0
- data/webpack/routes/HostgroupJobs/index.js +26 -0
- data/webpack/routes/routes.js +10 -0
- data/webpack/testHelper.js +165 -0
- data/webpack/toastHelper.js +4 -0
- metadata +127 -54
- data/app/assets/images/foreman_ansible/Ansible.png +0 -0
- data/app/models/foreman_ansible/fact_name.rb +0 -16
- data/app/models/setting/ansible.rb +0 -106
- data/app/services/foreman_ansible/fact_importer.rb +0 -99
- data/app/services/foreman_ansible/fact_parser.rb +0 -126
- data/app/services/foreman_ansible/fact_sparser.rb +0 -37
- data/app/services/foreman_ansible/operating_system_parser.rb +0 -102
- data/app/services/foreman_ansible/structured_fact_importer.rb +0 -25
- data/test/unit/services/fact_importer_test.rb +0 -52
- data/test/unit/services/fact_parser_test.rb +0 -281
- data/test/unit/services/fact_sparser_test.rb +0 -24
- data/test/unit/services/structured_fact_importer_test.rb +0 -30
- data/webpack/components/AnsibleRolesAndVariables/__test__/__snapshots__/AnsibleRolesAndVariablesImport.test.js.snap +0 -177
@@ -0,0 +1,74 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
3
|
+
import '@testing-library/jest-dom';
|
4
|
+
import userEvent from '@testing-library/user-event';
|
5
|
+
|
6
|
+
import {
|
7
|
+
withRedux,
|
8
|
+
withMockedProvider,
|
9
|
+
tick,
|
10
|
+
historyMock,
|
11
|
+
} from '../../../../../testHelper';
|
12
|
+
import {
|
13
|
+
deleteMocks,
|
14
|
+
mocks,
|
15
|
+
hostId,
|
16
|
+
hostAttrs,
|
17
|
+
} from './AnsibleVariableOverrides.fixtures';
|
18
|
+
|
19
|
+
import * as toasts from '../../../../../toastHelper';
|
20
|
+
|
21
|
+
import AnsibleVariableOverrides from '../';
|
22
|
+
|
23
|
+
const TestComponent = withRedux(withMockedProvider(AnsibleVariableOverrides));
|
24
|
+
|
25
|
+
describe('AnsibleVariableOverrides', () => {
|
26
|
+
it('should open and close delete modal', async () => {
|
27
|
+
render(
|
28
|
+
<TestComponent
|
29
|
+
mocks={mocks}
|
30
|
+
hostId={hostId}
|
31
|
+
hostAttrs={hostAttrs}
|
32
|
+
history={historyMock}
|
33
|
+
/>
|
34
|
+
);
|
35
|
+
await waitFor(tick);
|
36
|
+
userEvent.click(screen.getAllByRole('button', { name: 'Actions' })[0]);
|
37
|
+
userEvent.click(screen.getByText('Delete'));
|
38
|
+
await waitFor(tick);
|
39
|
+
expect(
|
40
|
+
screen.getByText('Delete Ansible Variable Override')
|
41
|
+
).toBeInTheDocument();
|
42
|
+
userEvent.click(screen.getByText('Cancel'));
|
43
|
+
await waitFor(tick);
|
44
|
+
expect(
|
45
|
+
screen.queryByText('Delete Ansible Variable Override')
|
46
|
+
).not.toBeInTheDocument();
|
47
|
+
});
|
48
|
+
it('should delete override', async () => {
|
49
|
+
const showToast = jest.fn();
|
50
|
+
jest.spyOn(toasts, 'showToast').mockImplementation(showToast);
|
51
|
+
render(
|
52
|
+
<TestComponent
|
53
|
+
mocks={mocks.concat(deleteMocks)}
|
54
|
+
hostId={hostId}
|
55
|
+
hostAttrs={hostAttrs}
|
56
|
+
history={historyMock}
|
57
|
+
/>
|
58
|
+
);
|
59
|
+
await waitFor(tick);
|
60
|
+
expect(screen.queryByText('21')).toBeInTheDocument();
|
61
|
+
userEvent.click(screen.getAllByRole('button', { name: 'Actions' })[0]);
|
62
|
+
userEvent.click(screen.getByText('Delete'));
|
63
|
+
await waitFor(tick);
|
64
|
+
userEvent.click(screen.getByText('Confirm'));
|
65
|
+
await waitFor(tick);
|
66
|
+
expect(showToast).toHaveBeenCalledWith({
|
67
|
+
type: 'success',
|
68
|
+
message: 'Ansible variable override was successfully deleted.',
|
69
|
+
});
|
70
|
+
await waitFor(tick);
|
71
|
+
expect(screen.queryByText('21')).not.toBeInTheDocument();
|
72
|
+
expect(screen.queryByText('101')).toBeInTheDocument();
|
73
|
+
});
|
74
|
+
});
|
@@ -0,0 +1,188 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
3
|
+
import '@testing-library/jest-dom';
|
4
|
+
import userEvent from '@testing-library/user-event';
|
5
|
+
|
6
|
+
import {
|
7
|
+
withRedux,
|
8
|
+
withMockedProvider,
|
9
|
+
tick,
|
10
|
+
historyMock,
|
11
|
+
} from '../../../../../testHelper';
|
12
|
+
import {
|
13
|
+
mocks,
|
14
|
+
updateMocks,
|
15
|
+
createMocks,
|
16
|
+
updateErrorMocks,
|
17
|
+
updateValidationMocks,
|
18
|
+
hostId,
|
19
|
+
hostAttrs,
|
20
|
+
} from './AnsibleVariableOverrides.fixtures';
|
21
|
+
|
22
|
+
import * as toasts from '../../../../../toastHelper';
|
23
|
+
|
24
|
+
import AnsibleVariableOverrides from '../';
|
25
|
+
|
26
|
+
const TestComponent = withRedux(withMockedProvider(AnsibleVariableOverrides));
|
27
|
+
|
28
|
+
describe('AnsibleVariableOverrides', () => {
|
29
|
+
it('edit existing override', async () => {
|
30
|
+
const showToast = jest.fn();
|
31
|
+
jest.spyOn(toasts, 'showToast').mockImplementation(showToast);
|
32
|
+
|
33
|
+
render(
|
34
|
+
<TestComponent
|
35
|
+
mocks={mocks.concat(updateMocks)}
|
36
|
+
hostId={hostId}
|
37
|
+
hostAttrs={hostAttrs}
|
38
|
+
history={historyMock}
|
39
|
+
/>
|
40
|
+
);
|
41
|
+
|
42
|
+
await waitFor(tick);
|
43
|
+
expect(screen.getByText('21')).toBeInTheDocument();
|
44
|
+
userEvent.click(
|
45
|
+
screen.getAllByRole('button', { name: 'Edit override button' })[0]
|
46
|
+
);
|
47
|
+
userEvent.type(
|
48
|
+
screen.getByRole('textbox', { name: 'Edit override field' }),
|
49
|
+
'77'
|
50
|
+
);
|
51
|
+
userEvent.click(
|
52
|
+
screen.getAllByRole('button', {
|
53
|
+
name: 'Submit editing override button',
|
54
|
+
})[0]
|
55
|
+
);
|
56
|
+
await waitFor(tick);
|
57
|
+
expect(showToast).toHaveBeenCalledWith({
|
58
|
+
type: 'success',
|
59
|
+
message: 'Ansible variable override successfully changed.',
|
60
|
+
});
|
61
|
+
expect(screen.queryByText('21')).not.toBeInTheDocument();
|
62
|
+
expect(screen.getByText('2177')).toBeInTheDocument();
|
63
|
+
});
|
64
|
+
it('should show unexpected errors', async () => {
|
65
|
+
const showToast = jest.fn();
|
66
|
+
jest.spyOn(toasts, 'showToast').mockImplementation(showToast);
|
67
|
+
|
68
|
+
render(
|
69
|
+
<TestComponent
|
70
|
+
mocks={mocks.concat(updateErrorMocks)}
|
71
|
+
hostId={hostId}
|
72
|
+
hostAttrs={hostAttrs}
|
73
|
+
history={historyMock}
|
74
|
+
/>
|
75
|
+
);
|
76
|
+
await waitFor(tick);
|
77
|
+
userEvent.click(
|
78
|
+
screen.getAllByRole('button', { name: 'Edit override button' })[0]
|
79
|
+
);
|
80
|
+
userEvent.type(
|
81
|
+
screen.getByRole('textbox', { name: 'Edit override field' }),
|
82
|
+
'77'
|
83
|
+
);
|
84
|
+
userEvent.click(
|
85
|
+
screen.getAllByRole('button', {
|
86
|
+
name: 'Submit editing override button',
|
87
|
+
})[0]
|
88
|
+
);
|
89
|
+
await waitFor(tick);
|
90
|
+
expect(showToast).toHaveBeenCalledWith({
|
91
|
+
type: 'error',
|
92
|
+
message:
|
93
|
+
'There was a following error when changing Ansible variable override: Not enough minerals',
|
94
|
+
});
|
95
|
+
expect(
|
96
|
+
screen.getByRole('textbox', { name: 'Edit override field' })
|
97
|
+
).toHaveValue('2177');
|
98
|
+
userEvent.click(
|
99
|
+
screen.getAllByRole('button', {
|
100
|
+
name: 'Cancel editing override button',
|
101
|
+
})[0]
|
102
|
+
);
|
103
|
+
expect(screen.getByText('21')).toBeInTheDocument();
|
104
|
+
});
|
105
|
+
it('should show client validations', async () => {
|
106
|
+
render(
|
107
|
+
<TestComponent
|
108
|
+
mocks={mocks}
|
109
|
+
hostId={hostId}
|
110
|
+
hostAttrs={hostAttrs}
|
111
|
+
history={historyMock}
|
112
|
+
/>
|
113
|
+
);
|
114
|
+
await waitFor(tick);
|
115
|
+
userEvent.click(
|
116
|
+
screen.getAllByRole('button', { name: 'Edit override button' })[1]
|
117
|
+
);
|
118
|
+
const input = screen.getByRole('textbox', { name: 'Edit override field' });
|
119
|
+
const submitBtn = screen.getAllByRole('button', {
|
120
|
+
name: 'Submit editing override button',
|
121
|
+
})[1];
|
122
|
+
userEvent.clear(input);
|
123
|
+
expect(screen.getByText('is required')).toBeInTheDocument();
|
124
|
+
expect(submitBtn).toBeDisabled();
|
125
|
+
userEvent.type(input, 'd');
|
126
|
+
expect(
|
127
|
+
screen.getByText('Invalid, expected one of: a,b,c')
|
128
|
+
).toBeInTheDocument();
|
129
|
+
expect(submitBtn).toBeDisabled();
|
130
|
+
userEvent.clear(input);
|
131
|
+
userEvent.type(input, 'c');
|
132
|
+
expect(submitBtn).not.toBeDisabled();
|
133
|
+
});
|
134
|
+
it('should show server validations', async () => {
|
135
|
+
render(
|
136
|
+
<TestComponent
|
137
|
+
mocks={mocks.concat(updateValidationMocks)}
|
138
|
+
hostId={hostId}
|
139
|
+
hostAttrs={hostAttrs}
|
140
|
+
history={historyMock}
|
141
|
+
/>
|
142
|
+
);
|
143
|
+
await waitFor(tick);
|
144
|
+
userEvent.click(
|
145
|
+
screen.getAllByRole('button', { name: 'Edit override button' })[0]
|
146
|
+
);
|
147
|
+
const input = screen.getByRole('textbox', { name: 'Edit override field' });
|
148
|
+
userEvent.clear(input);
|
149
|
+
userEvent.type(input, 'foo');
|
150
|
+
userEvent.click(
|
151
|
+
screen.getAllByRole('button', {
|
152
|
+
name: 'Submit editing override button',
|
153
|
+
})[0]
|
154
|
+
);
|
155
|
+
await waitFor(tick);
|
156
|
+
expect(screen.getByText('is invalid integer')).toBeInTheDocument();
|
157
|
+
});
|
158
|
+
it('should create new override', async () => {
|
159
|
+
const showToast = jest.fn();
|
160
|
+
jest.spyOn(toasts, 'showToast').mockImplementation(showToast);
|
161
|
+
|
162
|
+
render(
|
163
|
+
<TestComponent
|
164
|
+
mocks={mocks.concat(createMocks)}
|
165
|
+
hostId={hostId}
|
166
|
+
hostAttrs={hostAttrs}
|
167
|
+
history={historyMock}
|
168
|
+
/>
|
169
|
+
);
|
170
|
+
await waitFor(tick);
|
171
|
+
userEvent.click(
|
172
|
+
screen.getAllByRole('button', { name: 'Edit override button' })[1]
|
173
|
+
);
|
174
|
+
const input = screen.getByRole('textbox', { name: 'Edit override field' });
|
175
|
+
userEvent.clear(input);
|
176
|
+
userEvent.type(input, 'b');
|
177
|
+
userEvent.click(
|
178
|
+
screen.getAllByRole('button', {
|
179
|
+
name: 'Submit editing override button',
|
180
|
+
})[1]
|
181
|
+
);
|
182
|
+
await waitFor(tick);
|
183
|
+
expect(showToast).toHaveBeenCalledWith({
|
184
|
+
type: 'success',
|
185
|
+
message: 'Ansible variable override successfully changed.',
|
186
|
+
});
|
187
|
+
});
|
188
|
+
});
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
4
|
+
|
5
|
+
import { useQuery } from '@apollo/client';
|
6
|
+
import variableOverrides from '../../../../graphql/queries/hostVariableOverrides.gql';
|
7
|
+
import AnsibleVariableOverridesTable from './AnsibleVariableOverridesTable';
|
8
|
+
import {
|
9
|
+
useParamsToVars,
|
10
|
+
useCurrentPagination,
|
11
|
+
} from '../../../../helpers/pageParamsHelper';
|
12
|
+
|
13
|
+
import { encodeId } from '../../../../globalIdHelper';
|
14
|
+
import './AnsibleVariableOverrides.scss';
|
15
|
+
|
16
|
+
const AnsibleVariableOverrides = ({ hostId, hostAttrs, history }) => {
|
17
|
+
const hostGlobalId = encodeId('Host', hostId);
|
18
|
+
const pagination = useCurrentPagination(history);
|
19
|
+
|
20
|
+
const useFetchFn = () =>
|
21
|
+
useQuery(variableOverrides, {
|
22
|
+
variables: {
|
23
|
+
id: hostGlobalId,
|
24
|
+
match: `fqdn=${hostAttrs.name}`,
|
25
|
+
...useParamsToVars(history),
|
26
|
+
},
|
27
|
+
fetchPolicy: 'network-only',
|
28
|
+
nextFetchPolicy: 'cache-first',
|
29
|
+
});
|
30
|
+
|
31
|
+
const renameData = data => ({
|
32
|
+
variables: data.host.ansibleVariablesWithOverrides.nodes,
|
33
|
+
totalCount: data.host.ansibleVariablesWithOverrides.totalCount,
|
34
|
+
});
|
35
|
+
|
36
|
+
return (
|
37
|
+
<AnsibleVariableOverridesTable
|
38
|
+
hostId={hostId}
|
39
|
+
hostAttrs={hostAttrs}
|
40
|
+
hostGlobalId={hostGlobalId}
|
41
|
+
renameData={renameData}
|
42
|
+
fetchFn={useFetchFn}
|
43
|
+
renamedDataPath="variables"
|
44
|
+
emptyStateTitle={__('No Ansible Variables found for Host')}
|
45
|
+
permissions={['view_ansible_variables']}
|
46
|
+
pagination={pagination}
|
47
|
+
history={history}
|
48
|
+
/>
|
49
|
+
);
|
50
|
+
};
|
51
|
+
|
52
|
+
AnsibleVariableOverrides.propTypes = {
|
53
|
+
hostId: PropTypes.number.isRequired,
|
54
|
+
hostAttrs: PropTypes.object.isRequired,
|
55
|
+
history: PropTypes.object.isRequired,
|
56
|
+
};
|
57
|
+
|
58
|
+
export default AnsibleVariableOverrides;
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import { useQuery, useMutation } from '@apollo/client';
|
2
|
+
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
3
|
+
import jobsQuery from '../../../../graphql/queries/recurringJobs.gql';
|
4
|
+
import cancelRecurringLogic from '../../../../graphql/mutations/cancelRecurringLogic.gql';
|
5
|
+
import { showToast } from '../../../../toastHelper';
|
6
|
+
|
7
|
+
export const ansiblePurpose = (resourceName, resourceId) =>
|
8
|
+
`ansible-${resourceName}-${resourceId}`;
|
9
|
+
|
10
|
+
const jobSearch = (resourceName, resourceId, statusSearch) =>
|
11
|
+
`recurring = true && pattern_template_name = "Ansible Roles - Ansible Default" && ${statusSearch} && recurring_logic.purpose = ${ansiblePurpose(
|
12
|
+
resourceName,
|
13
|
+
resourceId
|
14
|
+
)}`;
|
15
|
+
|
16
|
+
export const scheduledJobsSearch = (resourceName, resourceId) =>
|
17
|
+
jobSearch(resourceName, resourceId, 'status = queued');
|
18
|
+
export const previousJobsSearch = (resourceName, resourceId) =>
|
19
|
+
jobSearch(resourceName, resourceId, 'status != queued');
|
20
|
+
|
21
|
+
const fetchJobsFn = (searchFn, pagination = {}) => componentProps =>
|
22
|
+
useQuery(jobsQuery, {
|
23
|
+
variables: {
|
24
|
+
search: searchFn(componentProps.resourceName, componentProps.resourceId),
|
25
|
+
...pagination,
|
26
|
+
},
|
27
|
+
});
|
28
|
+
|
29
|
+
export const fetchRecurringFn = fetchJobsFn(scheduledJobsSearch);
|
30
|
+
export const fetchPreviousFn = pagination =>
|
31
|
+
fetchJobsFn(previousJobsSearch, pagination);
|
32
|
+
|
33
|
+
export const renameData = data => ({
|
34
|
+
jobs: data.jobInvocations.nodes,
|
35
|
+
totalCount: data.jobInvocations.totalCount,
|
36
|
+
});
|
37
|
+
|
38
|
+
export const joinErrors = errors => errors.map(err => err.message).join(', ');
|
39
|
+
|
40
|
+
const formatError = error =>
|
41
|
+
sprintf(
|
42
|
+
__('There was a following error when deleting Ansible config job: %s'),
|
43
|
+
error
|
44
|
+
);
|
45
|
+
|
46
|
+
const onError = error => {
|
47
|
+
showToast({ type: 'danger', message: formatError(error) });
|
48
|
+
};
|
49
|
+
|
50
|
+
const onCompleted = data => {
|
51
|
+
const { errors } = data.cancelRecurringLogic;
|
52
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
53
|
+
showToast({
|
54
|
+
type: 'danger',
|
55
|
+
message: formatError(joinErrors(errors)),
|
56
|
+
});
|
57
|
+
} else {
|
58
|
+
showToast({
|
59
|
+
type: 'success',
|
60
|
+
message: __('Ansible job was successfully canceled.'),
|
61
|
+
});
|
62
|
+
}
|
63
|
+
};
|
64
|
+
|
65
|
+
export const useCancelMutation = (resourceName, resourceId) =>
|
66
|
+
useMutation(cancelRecurringLogic, {
|
67
|
+
onCompleted,
|
68
|
+
onError,
|
69
|
+
refetchQueries: [
|
70
|
+
{
|
71
|
+
query: jobsQuery,
|
72
|
+
variables: { search: previousJobsSearch(resourceName, resourceId) },
|
73
|
+
},
|
74
|
+
{
|
75
|
+
query: jobsQuery,
|
76
|
+
variables: { search: scheduledJobsSearch(resourceName, resourceId) },
|
77
|
+
},
|
78
|
+
],
|
79
|
+
});
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import * as Yup from 'yup';
|
2
|
+
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
3
|
+
|
4
|
+
import { showToast } from '../../../../toastHelper';
|
5
|
+
import { ansiblePurpose, joinErrors } from './JobsTabHelper';
|
6
|
+
|
7
|
+
export const frequencyOpts = [
|
8
|
+
{ id: 'hourly', name: __('hourly') },
|
9
|
+
{ id: 'daily', name: __('daily') },
|
10
|
+
{ id: 'weekly', name: __('weekly') },
|
11
|
+
{ id: 'monthly', name: __('monthly') },
|
12
|
+
];
|
13
|
+
|
14
|
+
export const rangeValidator = date => {
|
15
|
+
if (date < new Date()) {
|
16
|
+
return __('Must not be in the past');
|
17
|
+
}
|
18
|
+
return '';
|
19
|
+
};
|
20
|
+
|
21
|
+
export const createValidationSchema = () => {
|
22
|
+
const cantBeBlank = __("can't be blank");
|
23
|
+
|
24
|
+
return Yup.object().shape({
|
25
|
+
repeat: Yup.string().required(cantBeBlank),
|
26
|
+
startTime: Yup.string().required(cantBeBlank),
|
27
|
+
startDate: Yup.string().required(cantBeBlank),
|
28
|
+
});
|
29
|
+
};
|
30
|
+
|
31
|
+
export const toCron = (date, repeat) => {
|
32
|
+
switch (repeat) {
|
33
|
+
case 'hourly':
|
34
|
+
return `${date.getMinutes()} * * * *`;
|
35
|
+
case 'daily':
|
36
|
+
return `${date.getMinutes()} ${date.getHours()} * * *`;
|
37
|
+
case 'weekly':
|
38
|
+
return `${date.getMinutes()} ${date.getHours()} * * ${date.getDay()}`;
|
39
|
+
case 'monthly':
|
40
|
+
return `${date.getMinutes()} ${date.getHours()} ${date.getDate()} * *`;
|
41
|
+
default:
|
42
|
+
return `${date.getMinutes()} * * * *`;
|
43
|
+
}
|
44
|
+
};
|
45
|
+
|
46
|
+
export const toVars = (resourceName, resourceId, date, repeat) => {
|
47
|
+
const targeting =
|
48
|
+
resourceName === 'host'
|
49
|
+
? { hostIds: [resourceId] }
|
50
|
+
: { searchQuery: `hostgroup_id = ${resourceId}` };
|
51
|
+
|
52
|
+
return {
|
53
|
+
variables: {
|
54
|
+
jobInvocation: {
|
55
|
+
...targeting,
|
56
|
+
feature: 'ansible_run_host',
|
57
|
+
targetingType: 'static_query',
|
58
|
+
scheduling: {
|
59
|
+
startAt: date,
|
60
|
+
},
|
61
|
+
recurrence: {
|
62
|
+
cronLine: toCron(date, repeat),
|
63
|
+
purpose: ansiblePurpose(resourceName, resourceId),
|
64
|
+
},
|
65
|
+
},
|
66
|
+
},
|
67
|
+
};
|
68
|
+
};
|
69
|
+
|
70
|
+
const formatError = error =>
|
71
|
+
sprintf(
|
72
|
+
__('There was a following error when creating Ansible job: %s'),
|
73
|
+
error
|
74
|
+
);
|
75
|
+
|
76
|
+
export const onSubmit = (callMutation, onClose, resourceName, resourceId) => (
|
77
|
+
values,
|
78
|
+
actions
|
79
|
+
) => {
|
80
|
+
const onCompleted = response => {
|
81
|
+
actions.setSubmitting(false);
|
82
|
+
const { errors } = response.data.createJobInvocation;
|
83
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
84
|
+
showToast({
|
85
|
+
type: 'danger',
|
86
|
+
message: formatError(joinErrors(errors)),
|
87
|
+
});
|
88
|
+
} else {
|
89
|
+
onClose();
|
90
|
+
showToast({
|
91
|
+
type: 'success',
|
92
|
+
message: __('Ansible job was successfully created.'),
|
93
|
+
});
|
94
|
+
}
|
95
|
+
};
|
96
|
+
|
97
|
+
const onError = error => {
|
98
|
+
actions.setSubmitting(false);
|
99
|
+
showToast({ type: 'danger', message: formatError(error) });
|
100
|
+
};
|
101
|
+
|
102
|
+
const date = new Date(`${values.startDate}T${values.startTime}`);
|
103
|
+
const variables = toVars(resourceName, resourceId, date, values.repeat);
|
104
|
+
// eslint-disable-next-line promise/prefer-await-to-then
|
105
|
+
callMutation(variables).then(onCompleted, onError);
|
106
|
+
};
|
@@ -0,0 +1,129 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { Formik, Field as FormikField } from 'formik';
|
4
|
+
import { useMutation } from '@apollo/client';
|
5
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
6
|
+
|
7
|
+
import {
|
8
|
+
Modal,
|
9
|
+
Button,
|
10
|
+
ModalVariant,
|
11
|
+
Spinner,
|
12
|
+
Form as PfForm,
|
13
|
+
} from '@patternfly/react-core';
|
14
|
+
import {
|
15
|
+
onSubmit,
|
16
|
+
createValidationSchema,
|
17
|
+
frequencyOpts,
|
18
|
+
rangeValidator,
|
19
|
+
} from './NewRecurringJobHelper';
|
20
|
+
|
21
|
+
import {
|
22
|
+
DatePickerField,
|
23
|
+
TimePickerField,
|
24
|
+
SelectField,
|
25
|
+
} from '../../../../formHelper';
|
26
|
+
|
27
|
+
import './NewRecurringJobModal.scss';
|
28
|
+
|
29
|
+
import { scheduledJobsSearch } from './JobsTabHelper';
|
30
|
+
|
31
|
+
import createJobInvocation from '../../../../graphql/mutations/createJobInvocation.gql';
|
32
|
+
import jobsQuery from '../../../../graphql/queries/recurringJobs.gql';
|
33
|
+
|
34
|
+
const NewRecurringJobModal = props => {
|
35
|
+
const { onClose, resourceId, resourceName } = props;
|
36
|
+
|
37
|
+
const [callMutation] = useMutation(createJobInvocation, {
|
38
|
+
refetchQueries: [
|
39
|
+
{
|
40
|
+
query: jobsQuery,
|
41
|
+
variables: { search: scheduledJobsSearch(resourceName, resourceId) },
|
42
|
+
},
|
43
|
+
],
|
44
|
+
});
|
45
|
+
|
46
|
+
return (
|
47
|
+
<Formik
|
48
|
+
validationSchema={createValidationSchema()}
|
49
|
+
onSubmit={onSubmit(callMutation, onClose, resourceName, resourceId)}
|
50
|
+
initialValues={{
|
51
|
+
startTime: '',
|
52
|
+
startDate: '',
|
53
|
+
repeat: '',
|
54
|
+
}}
|
55
|
+
>
|
56
|
+
{formProps => {
|
57
|
+
const actions = [
|
58
|
+
<Button
|
59
|
+
aria-label="submit creating job"
|
60
|
+
key="confirm"
|
61
|
+
variant="primary"
|
62
|
+
onClick={formProps.handleSubmit}
|
63
|
+
isDisabled={formProps.isSubmitting || !formProps.isValid}
|
64
|
+
>
|
65
|
+
{__('Submit')}
|
66
|
+
</Button>,
|
67
|
+
<Button
|
68
|
+
aria-label="cancel creating job"
|
69
|
+
key="cancel"
|
70
|
+
variant="link"
|
71
|
+
onClick={onClose}
|
72
|
+
isDisabled={formProps.isSubmitting}
|
73
|
+
>
|
74
|
+
{__('Cancel')}
|
75
|
+
</Button>,
|
76
|
+
];
|
77
|
+
|
78
|
+
if (formProps.isSubmitting) {
|
79
|
+
actions.push(<Spinner key="spinner" size="lg" />);
|
80
|
+
}
|
81
|
+
|
82
|
+
return (
|
83
|
+
<Modal
|
84
|
+
variant={ModalVariant.large}
|
85
|
+
title="Create New Recurring Ansible Run"
|
86
|
+
isOpen={props.isOpen}
|
87
|
+
className="foreman-modal modal-high"
|
88
|
+
showClose={false}
|
89
|
+
actions={actions}
|
90
|
+
disableFocusTrap
|
91
|
+
>
|
92
|
+
<PfForm>
|
93
|
+
<FormikField
|
94
|
+
name="repeat"
|
95
|
+
component={SelectField}
|
96
|
+
label="Repeat"
|
97
|
+
isRequired
|
98
|
+
selectItems={frequencyOpts}
|
99
|
+
/>
|
100
|
+
<FormikField
|
101
|
+
name="startTime"
|
102
|
+
component={TimePickerField}
|
103
|
+
label="Start Time"
|
104
|
+
isRequired
|
105
|
+
is24Hour
|
106
|
+
/>
|
107
|
+
<FormikField
|
108
|
+
name="startDate"
|
109
|
+
component={DatePickerField}
|
110
|
+
label="Start Date"
|
111
|
+
isRequired
|
112
|
+
validators={[rangeValidator]}
|
113
|
+
/>
|
114
|
+
</PfForm>
|
115
|
+
</Modal>
|
116
|
+
);
|
117
|
+
}}
|
118
|
+
</Formik>
|
119
|
+
);
|
120
|
+
};
|
121
|
+
|
122
|
+
NewRecurringJobModal.propTypes = {
|
123
|
+
onClose: PropTypes.func.isRequired,
|
124
|
+
resourceId: PropTypes.number.isRequired,
|
125
|
+
resourceName: PropTypes.string.isRequired,
|
126
|
+
isOpen: PropTypes.bool.isRequired,
|
127
|
+
};
|
128
|
+
|
129
|
+
export default NewRecurringJobModal;
|