foreman_acd 0.0.5 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +82 -27
  3. data/app/controllers/foreman_acd/ansible_playbooks_controller.rb +122 -0
  4. data/app/controllers/foreman_acd/api/v2/ansible_playbooks_controller.rb +71 -0
  5. data/app/controllers/foreman_acd/api/v2/app_definitions_controller.rb +1 -2
  6. data/app/controllers/foreman_acd/api/v2/app_instances_controller.rb +54 -0
  7. data/app/controllers/foreman_acd/api/v2/app_playbooks_controller.rb +0 -0
  8. data/app/controllers/foreman_acd/app_definitions_controller.rb +36 -4
  9. data/app/controllers/foreman_acd/app_instances_controller.rb +24 -90
  10. data/app/controllers/foreman_acd/concerns/ansible_playbook_parameters.rb +23 -0
  11. data/app/controllers/foreman_acd/concerns/app_definition_parameters.rb +1 -1
  12. data/app/controllers/foreman_acd/concerns/app_instance_parameters.rb +1 -1
  13. data/app/controllers/foreman_acd/remote_execution_controller.rb +49 -0
  14. data/app/controllers/ui_acd_controller.rb +11 -4
  15. data/app/models/foreman_acd/acd_provider.rb +27 -0
  16. data/app/models/foreman_acd/ansible_playbook.rb +50 -0
  17. data/app/models/foreman_acd/app_definition.rb +2 -1
  18. data/app/models/foreman_acd/app_instance.rb +7 -0
  19. data/app/services/foreman_acd/app_configurator.rb +57 -0
  20. data/app/services/foreman_acd/app_deployer.rb +139 -0
  21. data/app/services/foreman_acd/inventory_creator.rb +67 -0
  22. data/app/views/foreman_acd/ansible_playbooks/_form.html.erb +21 -0
  23. data/app/views/foreman_acd/ansible_playbooks/edit.html.erb +3 -0
  24. data/app/views/foreman_acd/ansible_playbooks/index.html.erb +30 -0
  25. data/app/views/foreman_acd/ansible_playbooks/new.html.erb +3 -0
  26. data/app/views/foreman_acd/api/v2/ansible_playbooks/base.json.rabl +3 -0
  27. data/app/views/foreman_acd/api/v2/ansible_playbooks/index.json.rabl +3 -0
  28. data/app/views/foreman_acd/api/v2/ansible_playbooks/show.json.rabl +3 -0
  29. data/app/views/foreman_acd/api/v2/app_definitions/base.json.rabl +3 -0
  30. data/app/views/foreman_acd/api/v2/app_definitions/index.json.rabl +3 -0
  31. data/app/views/foreman_acd/api/v2/app_definitions/show.json.rabl +3 -0
  32. data/app/views/foreman_acd/api/v2/app_instances/base.json.rabl +3 -0
  33. data/app/views/foreman_acd/api/v2/app_instances/index.json.rabl +3 -0
  34. data/app/views/foreman_acd/api/v2/app_instances/show.json.rabl +3 -0
  35. data/app/views/foreman_acd/app_definitions/_form.html.erb +25 -19
  36. data/app/views/foreman_acd/app_definitions/edit.html.erb +5 -0
  37. data/app/views/foreman_acd/app_definitions/import.html.erb +18 -0
  38. data/app/views/foreman_acd/app_definitions/index.html.erb +9 -5
  39. data/app/views/foreman_acd/app_instances/_form.html.erb +10 -8
  40. data/app/views/foreman_acd/app_instances/deploy.html.erb +19 -0
  41. data/app/views/foreman_acd/app_instances/index.html.erb +10 -5
  42. data/app/views/foreman_acd/app_instances/report.html.erb +19 -0
  43. data/app/views/templates/job/run_acd_ansible_playbook.erb +49 -0
  44. data/app/views/ui_acd/ansible_data.json.rabl +6 -0
  45. data/app/views/ui_acd/app.json.rabl +6 -2
  46. data/app/views/ui_acd/app_definition.json.rabl +1 -1
  47. data/app/views/ui_acd/{fdata.json.rabl → foreman_data.json.rabl} +1 -1
  48. data/config/routes.rb +36 -1
  49. data/db/migrate/20190610202252_create_app_definitions.rb +1 -3
  50. data/db/migrate/20190625140305_create_app_instances.rb +1 -1
  51. data/db/migrate/20200916091018_create_ansible_playbooks.rb +20 -0
  52. data/db/migrate/20200917120220_add_ansible_playbook_id.rb +14 -0
  53. data/db/migrate/20201016002819_add_ansible_vars_all_to_app_definitions.rb +5 -0
  54. data/db/migrate/20201016104338_add_ansible_vars_all_to_app_instances.rb +5 -0
  55. data/db/seeds.d/62_acd_proxy_feature.rb +6 -0
  56. data/db/seeds.d/75-job_templates.rb +8 -0
  57. data/lib/foreman_acd/engine.rb +7 -0
  58. data/lib/foreman_acd/plugin.rb +84 -13
  59. data/lib/foreman_acd/version.rb +1 -1
  60. data/package.json +1 -2
  61. data/webpack/components/ApplicationDefinition/ApplicationDefinition.js +378 -0
  62. data/webpack/components/ApplicationDefinition/ApplicationDefinition.scss +1 -0
  63. data/webpack/components/ApplicationDefinition/ApplicationDefinitionActions.js +295 -0
  64. data/webpack/components/ApplicationDefinition/ApplicationDefinitionConstants.js +14 -0
  65. data/webpack/components/ApplicationDefinition/ApplicationDefinitionHelper.js +26 -0
  66. data/webpack/components/ApplicationDefinition/ApplicationDefinitionReducer.js +243 -0
  67. data/webpack/components/ApplicationDefinition/ApplicationDefinitionSelectors.js +8 -0
  68. data/webpack/components/ApplicationDefinition/components/AnsiblePlaybookSelector.js +49 -0
  69. data/webpack/components/ApplicationDefinition/index.js +33 -0
  70. data/webpack/components/ApplicationInstance/ApplicationInstance.js +414 -0
  71. data/webpack/components/ApplicationInstance/ApplicationInstance.scss +11 -0
  72. data/webpack/components/ApplicationInstance/ApplicationInstanceActions.js +239 -0
  73. data/webpack/components/ApplicationInstance/ApplicationInstanceConstants.js +14 -0
  74. data/webpack/components/ApplicationInstance/ApplicationInstanceReducer.js +295 -0
  75. data/webpack/components/ApplicationInstance/ApplicationInstanceSelectors.js +9 -0
  76. data/webpack/components/ApplicationInstance/components/AppDefinitionSelector.js +49 -0
  77. data/webpack/components/ApplicationInstance/components/Service.js +30 -0
  78. data/webpack/components/ApplicationInstance/components/ServiceCounter.js +37 -0
  79. data/webpack/components/ApplicationInstance/index.js +35 -0
  80. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReport.js +155 -0
  81. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReport.scss +27 -0
  82. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportActions.js +86 -0
  83. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportConstants.js +4 -0
  84. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportReducer.js +52 -0
  85. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportSelectors.js +5 -0
  86. data/webpack/components/ApplicationInstanceReport/components/ReportViewer.js +26 -0
  87. data/webpack/components/ApplicationInstanceReport/index.js +27 -0
  88. data/webpack/components/ParameterSelection/ParameterSelection.js +121 -192
  89. data/webpack/components/ParameterSelection/ParameterSelection.scss +9 -0
  90. data/webpack/components/ParameterSelection/ParameterSelectionActions.js +80 -104
  91. data/webpack/components/ParameterSelection/ParameterSelectionConstants.js +15 -19
  92. data/webpack/components/ParameterSelection/ParameterSelectionHelper.js +3 -35
  93. data/webpack/components/ParameterSelection/ParameterSelectionReducer.js +116 -84
  94. data/webpack/components/ParameterSelection/ParameterSelectionSelectors.js +3 -7
  95. data/webpack/components/ParameterSelection/__fixtures__/parameterSelection.fixtures.js +12 -21
  96. data/webpack/components/ParameterSelection/__fixtures__/parameterSelectionData_1.fixtures.js +1 -1
  97. data/webpack/components/ParameterSelection/__fixtures__/parameterSelectionReducer.fixtures.js +3 -45
  98. data/webpack/components/ParameterSelection/__tests__/ParameterSelection.test.js +20 -0
  99. data/webpack/components/ParameterSelection/__tests__/ParameterSelectionReducer.test.js +22 -46
  100. data/webpack/components/ParameterSelection/__tests__/ParameterSelectionSelectors.test.js +6 -6
  101. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelection.test.js.snap +40 -265
  102. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelectionReducer.test.js.snap +11 -96
  103. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelectionSelectors.test.js.snap +3 -9
  104. data/webpack/components/ParameterSelection/index.js +6 -8
  105. data/webpack/components/common/AddTableEntry.js +30 -0
  106. data/webpack/components/common/DeleteTableEntry.js +38 -0
  107. data/webpack/components/common/EasyHeaderFormatter.js +18 -0
  108. data/webpack/components/common/EditTableEntry.js +49 -0
  109. data/webpack/components/common/ExtSelect.js +43 -0
  110. data/webpack/components/common/LockTableEntry.js +59 -0
  111. data/webpack/components/common/RailsData.js +27 -0
  112. data/webpack/components/common/__tests__/AddTableEntry.test.js +26 -0
  113. data/webpack/components/common/__tests__/DeleteTableEntry.test.js +29 -0
  114. data/webpack/components/common/__tests__/ExtSelect.test.js +38 -0
  115. data/webpack/components/common/__tests__/RailsData.test.js +16 -0
  116. data/webpack/components/common/__tests__/__snapshots__/AddParameter.test.js.snap +35 -0
  117. data/webpack/components/common/__tests__/__snapshots__/AddTableEntry.test.js.snap +35 -0
  118. data/webpack/components/common/__tests__/__snapshots__/DeleteParameter.test.js.snap +41 -0
  119. data/webpack/components/common/__tests__/__snapshots__/DeleteTableEntry.test.js.snap +41 -0
  120. data/webpack/components/common/__tests__/__snapshots__/ExtSelect.test.js.snap +18 -0
  121. data/webpack/components/common/__tests__/__snapshots__/RailsData.test.js.snap +10 -0
  122. data/webpack/helper.js +20 -0
  123. data/webpack/index.js +6 -0
  124. data/webpack/reducer.js +43 -3
  125. metadata +91 -39
@@ -0,0 +1,14 @@
1
+ export const APPLICATION_DEFINITION_INIT = 'INIT_APPLICATION_DEFINITION_INIT';
2
+ export const APPLICATION_DEFINITION_LOAD_ANSIBLE_DATA_REQUEST = 'APPLICATION_DEFINITION_LOAD_ANSIBLE_DATA_REQUEST';
3
+ export const APPLICATION_DEFINITION_LOAD_ANSIBLE_DATA_SUCCESS = 'APPLICATION_DEFINITION_LOAD_ANSIBLE_DATA_SUCCESS';
4
+ export const APPLICATION_DEFINITION_LOAD_ANSIBLE_DATA_FAILURE = 'APPLICATION_DEFINITION_LOAD_ANSIBLE_DATA_FAILURE';
5
+ export const APPLICATION_DEFINITION_SERVICE_DELETE = 'APPLICATION_DEFINITION_SERVICE_DELETE';
6
+ export const APPLICATION_DEFINITION_SERVICE_ADD = 'APPLICATION_DEFINITION_SERVICE_ADD';
7
+ export const APPLICATION_DEFINITION_SERVICE_EDIT_ACTIVATE = 'APPLICATION_DEFINITION_SERVICE_EDIT_ACTIVATE';
8
+ export const APPLICATION_DEFINITION_SERVICE_EDIT_CONFIRM = 'APPLICATION_DEFINITION_SERVICE_EDIT_CONFIRM';
9
+ export const APPLICATION_DEFINITION_SERVICE_EDIT_CHANGE = 'APPLICATION_DEFINITION_SERVICE_EDIT_CHANGE';
10
+ export const APPLICATION_DEFINITION_SERVICE_EDIT_CANCEL = 'APPLICATION_DEFINITION_SERVICE_EDIT_CANCEL';
11
+ export const APPLICATION_DEFINITION_FOREMAN_PARAMETER_SELECTION_MODAL_OPEN = 'APPLICATION_DEFINITION_FOREMAN_PARAMETER_SELECTION_MODAL_OPEN';
12
+ export const APPLICATION_DEFINITION_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE = 'APPLICATION_DEFINITION_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE';
13
+ export const APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN = 'APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN';
14
+ export const APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE = 'APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE';
@@ -0,0 +1,26 @@
1
+ export function transformAnsiblePlaybook(playbook) {
2
+
3
+ const ansiblePlaybook = new Object;
4
+ ansiblePlaybook.id = playbook.id;
5
+ ansiblePlaybook.name = playbook.name;
6
+ ansiblePlaybook.groups = {};
7
+
8
+ if (playbook.hasOwnProperty('groups')) {
9
+ Object.entries(playbook.groups).forEach(([group_name,group_vars]) => {
10
+ ansiblePlaybook.groups[group_name] = [];
11
+
12
+ let id=0;
13
+ Object.entries(group_vars).forEach(([var_name,var_value]) => {
14
+ let entry = {
15
+ id: id,
16
+ name: var_name,
17
+ value: var_value
18
+ }
19
+ ansiblePlaybook.groups[group_name].push(entry);
20
+ id += 1;
21
+ });
22
+ });
23
+ }
24
+
25
+ return ansiblePlaybook;
26
+ }
@@ -0,0 +1,243 @@
1
+ import Immutable from 'seamless-immutable';
2
+
3
+ import {
4
+ cloneDeep,
5
+ findIndex,
6
+ findLastIndex,
7
+ } from 'lodash';
8
+
9
+ import {
10
+ APPLICATION_DEFINITION_INIT,
11
+ APPLICATION_DEFINITION_LOAD_ANSIBLE_DATA_REQUEST,
12
+ APPLICATION_DEFINITION_LOAD_ANSIBLE_DATA_SUCCESS,
13
+ APPLICATION_DEFINITION_LOAD_ANSIBLE_DATA_FAILURE,
14
+ APPLICATION_DEFINITION_SERVICE_DELETE,
15
+ APPLICATION_DEFINITION_SERVICE_ADD,
16
+ APPLICATION_DEFINITION_SERVICE_EDIT_ACTIVATE,
17
+ APPLICATION_DEFINITION_SERVICE_EDIT_CONFIRM,
18
+ APPLICATION_DEFINITION_SERVICE_EDIT_CHANGE,
19
+ APPLICATION_DEFINITION_SERVICE_EDIT_CANCEL,
20
+ APPLICATION_DEFINITION_FOREMAN_PARAMETER_SELECTION_MODAL_OPEN,
21
+ APPLICATION_DEFINITION_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE,
22
+ APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN,
23
+ APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE,
24
+ } from './ApplicationDefinitionConstants';
25
+
26
+ import {
27
+ PARAMETER_SELECTION_PARAM_TYPE_FOREMAN,
28
+ PARAMETER_SELECTION_PARAM_TYPE_ANSIBLE,
29
+ } from '../ParameterSelection/ParameterSelectionConstants';
30
+
31
+ import {
32
+ transformAnsiblePlaybook,
33
+ } from './ApplicationDefinitionHelper';
34
+
35
+ export const initialState = Immutable({
36
+ name: false,
37
+ error: { errorMsg: '', status: '', statusText: '' },
38
+ });
39
+
40
+ const applicationDefinitionConf = (state = initialState, action) => {
41
+ const { payload } = action;
42
+
43
+ switch (action.type) {
44
+ case APPLICATION_DEFINITION_INIT: {
45
+ return state.merge(payload);
46
+ }
47
+
48
+ case APPLICATION_DEFINITION_LOAD_ANSIBLE_DATA_FAILURE: {
49
+ return state.merge({ error: payload.error, loading: false });
50
+ }
51
+ case APPLICATION_DEFINITION_LOAD_ANSIBLE_DATA_REQUEST: {
52
+ return state.set('loading', true);
53
+ }
54
+ case APPLICATION_DEFINITION_LOAD_ANSIBLE_DATA_SUCCESS: {
55
+ let newState = {};
56
+ let allVars = [];
57
+
58
+ const ansiblePlaybook = transformAnsiblePlaybook(payload);
59
+
60
+ if (ansiblePlaybook.hasOwnProperty('groups')) {
61
+ allVars = ansiblePlaybook.groups['all']
62
+ }
63
+
64
+ newState = {
65
+ ansiblePlaybook: ansiblePlaybook,
66
+ ansibleVarsAll: allVars
67
+ }
68
+
69
+ return state.merge(newState);
70
+ }
71
+ case APPLICATION_DEFINITION_SERVICE_ADD: {
72
+ let services = [];
73
+ let index = 1;
74
+
75
+ if ('services' in state && state.services !== undefined && state.services.length > 0) {
76
+ services = cloneDeep(state.services);
77
+ index = Math.max(...services.map(e => e.id)) + 1;
78
+ }
79
+
80
+ const newRow = { id: index, name: "", description: '', hostgroup: '',
81
+ ansibleGroup: '', minCount: '', maxCount: '',
82
+ foremanParameters: [], ansibleParameters: [], newEntry: true };
83
+ newRow.backup = cloneDeep(newRow)
84
+ services.push(newRow);
85
+
86
+ return state.merge({
87
+ editMode: true,
88
+ services: services
89
+ });
90
+ }
91
+ case APPLICATION_DEFINITION_SERVICE_DELETE: {
92
+ const services = state.services.filter(v => v.id !== payload.rowData.id);
93
+ return state.merge({
94
+ services: services,
95
+ })
96
+ }
97
+ case APPLICATION_DEFINITION_SERVICE_EDIT_ACTIVATE: {
98
+ const services = cloneDeep(state.services);
99
+ const index = findIndex(services, { id: payload.rowData.id });
100
+
101
+ services[index].backup = cloneDeep(services[index]);
102
+
103
+ return state.merge({
104
+ editMode: true,
105
+ services: services
106
+ });
107
+ }
108
+ case APPLICATION_DEFINITION_SERVICE_EDIT_CONFIRM: {
109
+ const services = cloneDeep(state.services);
110
+ const index = findIndex(services, { id: payload.rowData.id });
111
+
112
+ delete services[index].backup;
113
+ delete services[index].newEntry;
114
+
115
+ let ansibleParameters = [];
116
+ const selectedGroup = services[index].ansibleGroup;
117
+
118
+ if (selectedGroup) {
119
+ services[index].ansibleParameters = state.ansiblePlaybook.groups[selectedGroup];
120
+ }
121
+
122
+ return state.merge({
123
+ editMode: false,
124
+ services: services,
125
+ });
126
+ }
127
+ case APPLICATION_DEFINITION_SERVICE_EDIT_CHANGE: {
128
+ const services = cloneDeep(state.services);
129
+ const index = findIndex(services, { id: payload.rowData.id });
130
+
131
+ services[index][payload.property] = payload.value;
132
+
133
+ return state.set('services', services);
134
+ }
135
+ case APPLICATION_DEFINITION_SERVICE_EDIT_CANCEL: {
136
+ const services = cloneDeep(state.services);
137
+ const index = findIndex(services, { id: payload.rowData.id });
138
+
139
+ services[index] = cloneDeep(services[index].backup);
140
+ delete services[index].backup;
141
+
142
+ if (services[index].newEntry === true) {
143
+ services.splice(index, 1);
144
+ }
145
+
146
+ return state.merge({
147
+ editMode: false,
148
+ services: services
149
+ });
150
+ }
151
+ case APPLICATION_DEFINITION_FOREMAN_PARAMETER_SELECTION_MODAL_OPEN: {
152
+ let parametersData = {};
153
+
154
+ parametersData.paramDefinition = {
155
+ id: payload.rowData.id,
156
+ name: payload.rowData.name,
157
+ dataId: payload.rowData.hostgroup
158
+ }
159
+
160
+ parametersData.type = PARAMETER_SELECTION_PARAM_TYPE_FOREMAN;
161
+ parametersData.parameters = payload.rowData.foremanParameters;
162
+ parametersData.useDefaultValue = true;
163
+ parametersData.allowRowAdjustment = true;
164
+ parametersData.allowNameAdjustment = true;
165
+ parametersData.allowDescriptionAdjustment = true;
166
+
167
+ return state.merge({
168
+ parametersData: parametersData,
169
+ });
170
+ }
171
+ case APPLICATION_DEFINITION_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE: {
172
+ if (payload.mode == 'save') {
173
+ const services = cloneDeep(state.services);
174
+ const index = findIndex(services, { id: state.parametersData.paramDefinition.id });
175
+ services[index].foremanParameters = cloneDeep(payload.parameterSelection);
176
+
177
+ return state.merge({
178
+ parametersData: null,
179
+ services: services,
180
+ });
181
+ } else {
182
+ return state.merge({
183
+ parametersData: null,
184
+ });
185
+ }
186
+ }
187
+ case APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN: {
188
+ let parametersData = {};
189
+
190
+ if ((payload.hasOwnProperty('isAllGroup')) && (payload.isAllGroup == true)) {
191
+ parametersData.parameters = state.ansibleVarsAll;
192
+ parametersData.paramDefinition = {
193
+ isAllGroup: true,
194
+ }
195
+ } else {
196
+ parametersData.parameters = payload.rowData.ansibleParameters;
197
+ parametersData.paramDefinition = {
198
+ id: payload.rowData.id,
199
+ name: payload.rowData.name,
200
+ }
201
+ }
202
+
203
+ parametersData.type = PARAMETER_SELECTION_PARAM_TYPE_ANSIBLE;
204
+ parametersData.useDefaultValue = false;
205
+ parametersData.allowRowAdjustment = true;
206
+ parametersData.allowNameAdjustment = true;
207
+ parametersData.allowDescriptionAdjustment = true;
208
+
209
+ return state.merge({
210
+ parametersData: parametersData,
211
+ });
212
+ }
213
+ case APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE: {
214
+ let newState = {};
215
+ if (payload.mode == 'save') {
216
+ if ((state.parametersData.paramDefinition.hasOwnProperty('isAllGroup')) && (state.parametersData.paramDefinition.isAllGroup == true)) {
217
+ newState = {
218
+ parametersData: null,
219
+ ansibleVarsAll: cloneDeep(payload.parameterSelection),
220
+ };
221
+ } else {
222
+ const services = cloneDeep(state.services);
223
+ const index = findIndex(services, { id: state.parametersData.paramDefinition.id });
224
+ services[index].ansibleParameters = cloneDeep(payload.parameterSelection);
225
+
226
+ newState = {
227
+ parametersData: null,
228
+ services: services,
229
+ };
230
+ }
231
+ } else {
232
+ newState = {
233
+ parametersData: null,
234
+ };
235
+ }
236
+ return state.merge(newState);
237
+ }
238
+ default:
239
+ return state;
240
+ }
241
+ };
242
+
243
+ export default applicationDefinitionConf;
@@ -0,0 +1,8 @@
1
+ const applicationDefinitionConf = state => state.foremanAcd.applicationDefinitionConf;
2
+
3
+ export const selectEditMode = state => applicationDefinitionConf(state).editMode;
4
+ export const selectAnsiblePlaybook = state => applicationDefinitionConf(state).ansiblePlaybook;
5
+ export const selectServices = state => applicationDefinitionConf(state).services;
6
+ export const selectColumns = state => applicationDefinitionConf(state).columns;
7
+ export const selectParametersData = state => applicationDefinitionConf(state).parametersData;
8
+ export const selectAnsibleVarsAll = state => applicationDefinitionConf(state).ansibleVarsAll;
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import ExtSelect from '../../common/ExtSelect';
4
+ import RailsData from '../../common/RailsData'
5
+
6
+ const AnsiblePlaybookSelector= ({
7
+ label,
8
+ hidden,
9
+ editable,
10
+ viewText,
11
+ selectValue,
12
+ onChange,
13
+ options,
14
+ additionalData,
15
+ }) =>{
16
+ return (
17
+ <div className="form-group">
18
+ <label className="col-md-2 control-label">{label}</label>
19
+ <div className="col-md-4">
20
+ <ExtSelect
21
+ editable={editable}
22
+ viewText={viewText}
23
+ selectValue={selectValue}
24
+ onChange={onChange}
25
+ options={options}
26
+ additionalData={additionalData}
27
+ />
28
+ <RailsData
29
+ key='ansible_playbook_id'
30
+ view='app_definition'
31
+ parameter='acd_ansible_playbook_id'
32
+ value={selectValue}
33
+ />
34
+ </div>
35
+ </div>
36
+ );
37
+ };
38
+
39
+ AnsiblePlaybookSelector.propTypes = {
40
+ label: PropTypes.string.isRequired,
41
+ editable: PropTypes.bool.isRequired,
42
+ viewText: PropTypes.string,
43
+ selectValue: PropTypes.string,
44
+ onChange: PropTypes.func.isRequired,
45
+ options: PropTypes.object,
46
+ additionalData: PropTypes.object,
47
+ };
48
+
49
+ export default AnsiblePlaybookSelector;
@@ -0,0 +1,33 @@
1
+ import { bindActionCreators } from 'redux';
2
+ import { connect } from 'react-redux';
3
+
4
+ import './ApplicationDefinition.scss';
5
+ import ApplicationDefinition from './ApplicationDefinition';
6
+ import * as ApplicationDefinitionActions from './ApplicationDefinitionActions';
7
+
8
+ import {
9
+ selectEditMode,
10
+ selectAnsiblePlaybook,
11
+ selectServices,
12
+ selectColumns,
13
+ selectParametersData,
14
+ selectAnsibleVarsAll,
15
+ } from './ApplicationDefinitionSelectors';
16
+
17
+ const mapStateToProps = state => ({
18
+ editMode: selectEditMode(state),
19
+ ansiblePlaybook: selectAnsiblePlaybook(state),
20
+ services: selectServices(state),
21
+ columns: selectColumns(state),
22
+ parametersData: selectParametersData(state),
23
+ ansibleVarsAll: selectAnsibleVarsAll(state),
24
+ });
25
+
26
+ const mapDispatchToProps = dispatch =>
27
+ bindActionCreators(ApplicationDefinitionActions, dispatch);
28
+
29
+ export default connect(
30
+ mapStateToProps,
31
+ mapDispatchToProps
32
+ )(ApplicationDefinition);
33
+
@@ -0,0 +1,414 @@
1
+ import React, { useState } from 'react'
2
+ import PropTypes from 'prop-types';
3
+ import {
4
+ Icon,
5
+ Button,
6
+ } from 'patternfly-react';
7
+ import * as resolve from 'table-resolver';
8
+ import ForemanModal from 'foremanReact/components/ForemanModal';
9
+ import Select from 'foremanReact/components/common/forms/Select';
10
+ import ParameterSelection from '../ParameterSelection';
11
+ import AddTableEntry from '../common/AddTableEntry';
12
+ import DeleteTableEntry from '../common/DeleteTableEntry';
13
+ import RailsData from '../common/RailsData';
14
+ import EasyHeaderFormatter from '../common/EasyHeaderFormatter';
15
+ import AppDefinitionSelector from './components/AppDefinitionSelector';
16
+ import ServiceCounter from './components/ServiceCounter';
17
+ import { arrayToObject } from '../../helper';
18
+
19
+ import {
20
+ Table,
21
+ FormControl,
22
+ inlineEditFormatterFactory,
23
+ } from 'patternfly-react';
24
+
25
+ import {
26
+ PARAMETER_SELECTION_PARAM_TYPE_FOREMAN,
27
+ PARAMETER_SELECTION_PARAM_TYPE_ANSIBLE,
28
+ } from '../ParameterSelection/ParameterSelectionConstants';
29
+
30
+ class ApplicationInstance extends React.Component {
31
+
32
+ constructor(props) {
33
+ super(props);
34
+ }
35
+
36
+ isEditing({rowData}) {
37
+ return (rowData.backup !== undefined);
38
+ }
39
+
40
+ validateParameters() {
41
+ let result = true;
42
+ let msg = "";
43
+
44
+ this.props.hosts.forEach(h => {
45
+ if (h.parameters.map(e => e.value).filter(i => i == "").length > 0) {
46
+ result = false;
47
+
48
+ if (msg == "") {
49
+ msg += "For some hosts the values for some parameters are missing. Check the values for these hosts:\n";
50
+ }
51
+ msg += "- "+ h.hostname +"\n";
52
+ }
53
+ });
54
+
55
+ const invalidMinServices = this.props.services.filter(s => (Number(s.minCount) != 0) && (s.currentCount < s.minCount));
56
+ const invalidMaxServices = this.props.services.filter(s => (Number(s.maxCount) != 0) && (s.currentCount > s.maxCount));
57
+
58
+ if (invalidMinServices.length > 0 || invalidMaxServices.length > 0) {
59
+ result = false;
60
+
61
+ if (msg != "") {
62
+ msg += "\n";
63
+ }
64
+ msg += "Unachieved service counts: \n";
65
+
66
+ invalidMinServices.map(s => { msg += "- service "+ s.name +" expects at least "+ s.minCount +" configured hosts" });
67
+ invalidMaxServices.map(s => { msg += "- service "+ s.name +" expects no more than "+ s.maxCount +" configured hosts" });
68
+ }
69
+
70
+ if (result === false) {
71
+ window.alert(msg);
72
+ }
73
+ return result;
74
+ }
75
+
76
+ componentDidMount() {
77
+ const {
78
+ data: { mode, appDefinition, hosts, ansibleVarsAll, appDefinitionUrl },
79
+ initApplicationInstance,
80
+ addApplicationInstanceHost,
81
+ deleteApplicationInstanceHost,
82
+ activateEditApplicationInstanceHost,
83
+ changeEditApplicationInstanceHost,
84
+ openForemanParameterSelectionModal,
85
+ openAnsibleParameterSelectionModal,
86
+ loadApplicationDefinition,
87
+ } = this.props;
88
+
89
+ if (mode === 'editInstance') {
90
+ loadApplicationDefinition(appDefinition.id, { url: appDefinitionUrl });
91
+ }
92
+
93
+ const inlineEditButtonsFormatter = inlineEditFormatterFactory({
94
+ isEditing: additionalData => this.props.editMode,
95
+ renderValue: (value, additionalData) => (
96
+ <td style={{ padding: '2px' }}>
97
+ <Button
98
+ bsStyle="default"
99
+ onClick={() => activateEditApplicationInstanceHost(additionalData)}
100
+ >
101
+ <Icon type="pf" name="edit" title="edit entry" />
102
+ </Button>
103
+ &nbsp;
104
+ <Button
105
+ bsStyle="default"
106
+ onClick={() => openForemanParameterSelectionModal(additionalData)}
107
+ >
108
+ <Icon type="pf" name="settings" title="change parameters" />
109
+ </Button>
110
+ &nbsp;
111
+ <Button
112
+ bsStyle="default"
113
+ onClick={() => openAnsibleParameterSelectionModal(additionalData)}
114
+ >
115
+ <span title="change ansible variables">A</span>
116
+ </Button>
117
+ &nbsp;
118
+ <DeleteTableEntry
119
+ hidden={false}
120
+ disabled={false}
121
+ onDeleteTableEntry={deleteApplicationInstanceHost}
122
+ additionalData={additionalData}
123
+ />
124
+ </td>
125
+ ),
126
+ renderEdit: (value, additionalData) => (
127
+ <td style={{ padding: '2px' }}>
128
+ <Button bsStyle="default" disabled>
129
+ <Icon type="pf" name="edit" />
130
+ </Button>
131
+ &nbsp;
132
+ <Button bsStyle="default" disabled>
133
+ <Icon type="pf" name="settings" />
134
+ </Button>
135
+ &nbsp;
136
+ <Button bsStyle="default" disabled>
137
+ <span>A</span>
138
+ </Button>
139
+ &nbsp;
140
+ <DeleteTableEntry
141
+ hidden={false}
142
+ disabled={true}
143
+ onDeleteTableEntry={deleteApplicationInstanceHost}
144
+ additionalData={additionalData}
145
+ />
146
+ </td>
147
+ )
148
+ });
149
+ this.inlineEditButtonsFormatter = inlineEditButtonsFormatter;
150
+
151
+ this.headerFormatter = EasyHeaderFormatter;
152
+
153
+ const inlineEditFormatterImpl = {
154
+ renderValue: (value, additionalData) => (
155
+ <td>
156
+ <span className="static">{value}</span>
157
+ </td>
158
+ ),
159
+ renderEditText: (value, additionalData, subtype='text') => (
160
+ <td className="editing">
161
+ <FormControl
162
+ type={subtype}
163
+ defaultValue={value}
164
+ onBlur={e => changeEditApplicationInstanceHost(e.target.value, additionalData) }
165
+ />
166
+ </td>
167
+ ),
168
+ renderEditSelect: (value, additionalData, options) => (
169
+ <td className="editing">
170
+ <Select
171
+ value={value.toString()}
172
+ onChange={e => changeEditApplicationInstanceHost(e.target.value, additionalData) }
173
+ options={options}
174
+ allowClear
175
+ key="key"
176
+ />
177
+ </td>
178
+ )
179
+ };
180
+
181
+ const inlineEditFormatter = inlineEditFormatterFactory({
182
+ isEditing: additionalData => this.isEditing(additionalData),
183
+ renderValue: (value, additionalData) => {
184
+ let prettyValue = value;
185
+ if (additionalData.property == 'service') {
186
+ const serviceList = arrayToObject(this.props.services, "id", "name");
187
+ prettyValue = serviceList[value];
188
+ }
189
+ return inlineEditFormatterImpl.renderValue(prettyValue, additionalData)
190
+ },
191
+ renderEdit: (value, additionalData) => {
192
+ let prettyValue = value;
193
+ if (additionalData.property == 'service') {
194
+ const availableServices = this.props.services.filter(service => ((Number(service['maxCount']) == 0) || (service['currentCount'] < service['maxCount'])));
195
+ const serviceList = arrayToObject(availableServices, "id", "name");
196
+
197
+ if (additionalData.rowData.newEntry === true) {
198
+ return inlineEditFormatterImpl.renderEditSelect(value, additionalData, serviceList);
199
+ }
200
+ prettyValue = serviceList[value];
201
+ return inlineEditFormatterImpl.renderValue(prettyValue, additionalData)
202
+ }
203
+ return inlineEditFormatterImpl.renderEditText(prettyValue, additionalData);
204
+ }
205
+ });
206
+ this.inlineEditFormatter = inlineEditFormatter;
207
+
208
+ initApplicationInstance(
209
+ appDefinition,
210
+ hosts,
211
+ ansibleVarsAll,
212
+ this.headerFormatter,
213
+ this.inlineEditFormatter,
214
+ this.inlineEditButtonsFormatter,
215
+ );
216
+ };
217
+
218
+ render() {
219
+ const {
220
+ data: { mode, applications, organization, location, foremanDataUrl, appDefinitionUrl },
221
+ appDefinition,
222
+ services,
223
+ hosts,
224
+ columns,
225
+ addApplicationInstanceHost,
226
+ confirmEditApplicationInstanceHost,
227
+ cancelEditApplicationInstanceHost,
228
+ closeForemanParameterSelectionModal,
229
+ openAnsibleParameterSelectionModal,
230
+ closeAnsibleParameterSelectionModal,
231
+ loadApplicationDefinition,
232
+ } = this.props;
233
+
234
+ // Start from validation when pressing submit. This should be in componentDidMount() but
235
+ // unfortunatley then the event wasn't fired. To make sure, that the on-click is only added
236
+ // once, there is a workaround to check if a css class "bound" exists.
237
+ $('input[type="submit"][name="commit"]:not(.bound)').addClass('bound').on('click', () => this.validateParameters());
238
+
239
+ return (
240
+ <span>
241
+ <div class="service-counter">
242
+ <ServiceCounter
243
+ title="Service counts"
244
+ serviceList={ services }
245
+ hostList={ hosts }
246
+ />
247
+ </div>
248
+ <div>
249
+ <AppDefinitionSelector
250
+ label="Application Definition"
251
+ editable={ mode == 'newInstance' }
252
+ viewText={ appDefinition.name }
253
+ options={ applications }
254
+ onChange={ loadApplicationDefinition }
255
+ selectValue={ appDefinition.id.toString() }
256
+ additionalData={{url: appDefinitionUrl}}
257
+ />
258
+ </div>
259
+ <div className="form-group">
260
+ <AddTableEntry
261
+ hidden={ false }
262
+ disabled={ this.props.editMode }
263
+ onAddTableEntry={ addApplicationInstanceHost }
264
+ />
265
+ <Table.PfProvider
266
+ striped
267
+ bordered
268
+ hover
269
+ dataTable
270
+ inlineEdit
271
+ columns={columns}
272
+ components={{
273
+ body: {
274
+ row: Table.InlineEditRow,
275
+ cell: cellProps => cellProps.children
276
+ }
277
+ }}
278
+ >
279
+ <Table.Header headerRows={resolve.headerRows({ columns })} />
280
+ <Table.Body
281
+ rows={hosts}
282
+ rowKey="id"
283
+ onRow={(rowData, { rowIndex }) => ({
284
+ role: 'row',
285
+ isEditing: () => this.isEditing({ rowData }),
286
+ onCancel: () => cancelEditApplicationInstanceHost({ rowData, rowIndex }),
287
+ onConfirm: () => confirmEditApplicationInstanceHost({ rowData, rowIndex }),
288
+ last: rowIndex === services.length - 1
289
+ })}
290
+ />
291
+ </Table.PfProvider>
292
+ <AddTableEntry
293
+ hidden={ false }
294
+ disabled={ this.props.editMode }
295
+ onAddTableEntry={ addApplicationInstanceHost }
296
+ />
297
+ <span style={{ marginLeft: 30 }}>
298
+ Ansible group vars 'all':
299
+ <Button
300
+ style={{ marginLeft: 10 }}
301
+ bsStyle="default"
302
+ disabled={ this.props.editMode }
303
+ onClick={() => openAnsibleParameterSelectionModal({
304
+ isAllGroup: true
305
+ })}
306
+ >
307
+ <span title="change ansible variables for 'all'">A</span>
308
+ </Button>
309
+ </span>
310
+ </div>
311
+ <div>
312
+ <ForemanModal
313
+ id="AppInstanceForemanParamSelection"
314
+ dialogClassName="param_selection_modal"
315
+ title="Foreman Parameter specification for Application Instance"
316
+ >
317
+ <ForemanModal.Header closeButton={false}>
318
+ Parameter specification
319
+ </ForemanModal.Header>
320
+ {this.props.parametersData ? (
321
+ <ParameterSelection
322
+ paramType={ PARAMETER_SELECTION_PARAM_TYPE_FOREMAN }
323
+ location={ location }
324
+ organization={ organization }
325
+ paramDataUrl= { foremanDataUrl }
326
+ data={ this.props.parametersData }
327
+ />
328
+ ) : (<span>Empty</span>)
329
+ }
330
+ <ForemanModal.Footer>
331
+ <div>
332
+ <Button bsStyle="primary" onClick={() => closeForemanParameterSelectionModal({ mode: 'save' })}>Save</Button>
333
+ <Button bsStyle="default" onClick={() => closeForemanParameterSelectionModal({ mode: 'cancel' })}>Cancel</Button>
334
+ </div>
335
+ </ForemanModal.Footer>
336
+ </ForemanModal>
337
+ </div>
338
+ <div>
339
+ <ForemanModal
340
+ id="AppInstanceAnsibleParamSelection"
341
+ dialogClassName="param_selection_modal"
342
+ title="Ansible group variables for Application Instance"
343
+ >
344
+ <ForemanModal.Header closeButton={false}>
345
+ Parameter specification
346
+ </ForemanModal.Header>
347
+ {this.props.parametersData ? (
348
+ <ParameterSelection
349
+ paramType={ PARAMETER_SELECTION_PARAM_TYPE_ANSIBLE }
350
+ location={ location }
351
+ organization={ organization }
352
+ data={ this.props.parametersData }
353
+ />
354
+ ) : (<span>Empty</span>)
355
+ }
356
+ <ForemanModal.Footer>
357
+ <div>
358
+ <Button bsStyle="primary" onClick={() => closeAnsibleParameterSelectionModal({ mode: 'save' })}>Save</Button>
359
+ <Button bsStyle="default" onClick={() => closeAnsibleParameterSelectionModal({ mode: 'cancel' })}>Cancel</Button>
360
+ </div>
361
+ </ForemanModal.Footer>
362
+ </ForemanModal>
363
+ </div>
364
+ <RailsData
365
+ key='applications_instance'
366
+ view='app_instance'
367
+ parameter='hosts'
368
+ value={JSON.stringify(this.props.hosts)}
369
+ />
370
+ <RailsData
371
+ key='applications_instance'
372
+ view='app_instance'
373
+ parameter='ansible_vars_all'
374
+ value={JSON.stringify(this.props.ansibleVarsAll)}
375
+ />
376
+ </span>
377
+ )};
378
+ }
379
+
380
+ ApplicationInstance.defaultProps = {
381
+ error: {},
382
+ appDefinition: { "id": '', "name": '' },
383
+ editMode: false,
384
+ services: [],
385
+ hosts: [],
386
+ ansibleVarsAll: [],
387
+ parametersData: {},
388
+ columns: [],
389
+ editParamsOfRowId: null,
390
+ }
391
+
392
+ ApplicationInstance.propTypes = {
393
+ initApplicationInstance: PropTypes.func,
394
+ editMode: PropTypes.bool.isRequired,
395
+ services: PropTypes.array,
396
+ appDefinition: PropTypes.object,
397
+ columns: PropTypes.array,
398
+ hosts: PropTypes.array,
399
+ ansibleVarsAll: PropTypes.array,
400
+ loadApplicationDefinition: PropTypes.func,
401
+ addApplicationInstanceHost: PropTypes.func,
402
+ deleteApplicationInstanceHost: PropTypes.func,
403
+ activateEditApplicationInstanceHost: PropTypes.func,
404
+ confirmEditApplicationInstanceHost: PropTypes.func,
405
+ cancelEditApplicationInstanceHost: PropTypes.func,
406
+ changeEditApplicationInstanceHost: PropTypes.func,
407
+ openForemanParameterSelectionModal: PropTypes.func,
408
+ closeForemanParameterSelectionModal: PropTypes.func,
409
+ openAnsibleParameterSelectionModal: PropTypes.func,
410
+ closeAnsibleParameterSelectionModal: PropTypes.func,
411
+ parametersData: PropTypes.object,
412
+ };
413
+
414
+ export default ApplicationInstance;