codify-plugin-lib 1.0.147 → 1.0.149
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin/plugin.js +21 -0
- package/dist/resource/parsed-resource-settings.js +4 -4
- package/dist/resource/resource-controller.js +25 -28
- package/package.json +1 -1
- package/src/plugin/plugin.test.ts +43 -0
- package/src/plugin/plugin.ts +27 -1
- package/src/resource/parsed-resource-settings.ts +5 -4
- package/src/resource/resource-controller.ts +29 -28
package/dist/plugin/plugin.js
CHANGED
|
@@ -107,6 +107,27 @@ export class Plugin {
|
|
|
107
107
|
.validate(core, parameters);
|
|
108
108
|
validationResults.push(validation);
|
|
109
109
|
}
|
|
110
|
+
// Validate that if allow multiple is false, then only 1 of each resource exists
|
|
111
|
+
const countMap = data.configs.reduce((map, resource) => {
|
|
112
|
+
if (!map.has(resource.core.type)) {
|
|
113
|
+
map.set(resource.core.type, 0);
|
|
114
|
+
}
|
|
115
|
+
const count = map.get(resource.core.type);
|
|
116
|
+
map.set(resource.core.type, count + 1);
|
|
117
|
+
return map;
|
|
118
|
+
}, new Map());
|
|
119
|
+
const invalidMultipleConfigs = [...countMap.entries()].filter(([k, v]) => {
|
|
120
|
+
const controller = this.resourceControllers.get(k);
|
|
121
|
+
return !controller.parsedSettings.allowMultiple && v > 1;
|
|
122
|
+
});
|
|
123
|
+
if (invalidMultipleConfigs.length > 0) {
|
|
124
|
+
validationResults.push(...invalidMultipleConfigs.map(([k, v]) => ({
|
|
125
|
+
resourceType: k,
|
|
126
|
+
schemaValidationErrors: [],
|
|
127
|
+
customValidationErrorMessage: `Multiple of resource type: ${k} found in configs. Only allowed 1.`,
|
|
128
|
+
isValid: false,
|
|
129
|
+
})));
|
|
130
|
+
}
|
|
110
131
|
await this.crossValidateResources(data.configs);
|
|
111
132
|
return {
|
|
112
133
|
resourceValidations: validationResults
|
|
@@ -113,10 +113,10 @@ export class ParsedResourceSettings {
|
|
|
113
113
|
this.validateParameterEqualsFn(v, k);
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
if (Object.entries(this.parameterSettings).some(([k, v]) => v.type === 'stateful'
|
|
117
|
+
&& typeof this.settings.allowMultiple === 'object' && this.settings.allowMultiple?.identifyingParameters?.includes(k))) {
|
|
118
|
+
throw new Error(`Resource: ${this.id}. Stateful parameters are not allowed to be identifying parameters for allowMultiple.`);
|
|
119
|
+
}
|
|
120
120
|
const schema = this.settings.schema;
|
|
121
121
|
if (!this.settings.importAndDestroy && (schema?.oneOf
|
|
122
122
|
&& Array.isArray(schema.oneOf)
|
|
@@ -85,7 +85,6 @@ export class ResourceController {
|
|
|
85
85
|
// Short circuit here. If the resource is non-existent, there's no point checking stateful parameters
|
|
86
86
|
if (currentArray === null
|
|
87
87
|
|| currentArray === undefined
|
|
88
|
-
|| this.settings.allowMultiple // Stateful parameters are not supported currently if allowMultiple is true
|
|
89
88
|
|| currentArray.length === 0
|
|
90
89
|
|| currentArray.filter(Boolean).length === 0) {
|
|
91
90
|
return Plan.calculate({
|
|
@@ -97,12 +96,12 @@ export class ResourceController {
|
|
|
97
96
|
isStateful,
|
|
98
97
|
});
|
|
99
98
|
}
|
|
100
|
-
// Refresh stateful parameters. These parameters have state external to the resource.
|
|
101
|
-
//
|
|
102
|
-
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters, allParameters);
|
|
99
|
+
// Refresh stateful parameters. These parameters have state external to the resource. Each variation of the
|
|
100
|
+
// current parameters (each array element) is passed into the stateful parameter refresh.
|
|
101
|
+
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters, currentArray, allParameters);
|
|
103
102
|
return Plan.calculate({
|
|
104
103
|
desired,
|
|
105
|
-
currentArray:
|
|
104
|
+
currentArray: currentArray.map((c, idx) => ({ ...c, ...statefulCurrentParameters[idx] })),
|
|
106
105
|
state,
|
|
107
106
|
core,
|
|
108
107
|
settings: this.parsedSettings,
|
|
@@ -166,25 +165,21 @@ export class ResourceController {
|
|
|
166
165
|
};
|
|
167
166
|
// Parse data from the user supplied config
|
|
168
167
|
const parsedConfig = new ConfigParser(parametersToRefresh, null, this.parsedSettings.statefulParameters);
|
|
169
|
-
const { allNonStatefulParameters, allStatefulParameters, } = parsedConfig;
|
|
168
|
+
const { allParameters, allNonStatefulParameters, allStatefulParameters, } = parsedConfig;
|
|
170
169
|
const currentParametersArray = await this.refreshNonStatefulParameters(allNonStatefulParameters);
|
|
171
170
|
if (currentParametersArray === null
|
|
172
171
|
|| currentParametersArray === undefined
|
|
173
|
-
|| this.settings.allowMultiple // Stateful parameters are not supported currently if allowMultiple is true
|
|
174
172
|
|| currentParametersArray.filter(Boolean).length === 0) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters, currentParametersArray, allParameters);
|
|
176
|
+
const resultParametersArray = currentParametersArray
|
|
177
|
+
?.map((r, idx) => ({ ...r, ...statefulCurrentParameters[idx] }));
|
|
178
|
+
for (const result of resultParametersArray) {
|
|
179
|
+
await this.applyTransformParameters(result, true);
|
|
180
|
+
this.removeDefaultValues(result, parameters);
|
|
182
181
|
}
|
|
183
|
-
|
|
184
|
-
const resultParameters = { ...currentParametersArray[0], ...statefulCurrentParameters };
|
|
185
|
-
await this.applyTransformParameters(resultParameters, true);
|
|
186
|
-
this.removeDefaultValues(resultParameters, parameters);
|
|
187
|
-
return [{ core, parameters: resultParameters }];
|
|
182
|
+
return resultParametersArray?.map((r) => ({ core, parameters: r }));
|
|
188
183
|
}
|
|
189
184
|
async applyCreate(plan) {
|
|
190
185
|
await this.resource.create(plan);
|
|
@@ -296,17 +291,19 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
296
291
|
}
|
|
297
292
|
// Refresh stateful parameters
|
|
298
293
|
// This refreshes parameters that are stateful (they can be added, deleted separately from the resource)
|
|
299
|
-
async refreshStatefulParameters(statefulParametersConfig, allParameters) {
|
|
300
|
-
const result = {};
|
|
294
|
+
async refreshStatefulParameters(statefulParametersConfig, currentArray, allParameters) {
|
|
295
|
+
const result = Array.from({ length: currentArray.length }, () => ({}));
|
|
301
296
|
const sortedEntries = Object.entries(statefulParametersConfig)
|
|
302
297
|
.sort(([key1], [key2]) => this.parsedSettings.statefulParameterOrder.get(key1) - this.parsedSettings.statefulParameterOrder.get(key2));
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
298
|
+
for (const [idx, refreshedParams] of currentArray.entries()) {
|
|
299
|
+
await Promise.all(sortedEntries.map(async ([key, desiredValue]) => {
|
|
300
|
+
const statefulParameter = this.parsedSettings.statefulParameters.get(key);
|
|
301
|
+
if (!statefulParameter) {
|
|
302
|
+
throw new Error(`Stateful parameter ${key} was not found`);
|
|
303
|
+
}
|
|
304
|
+
result[idx][key] = await statefulParameter.refresh(desiredValue ?? null, { ...allParameters, ...refreshedParams });
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
310
307
|
return result;
|
|
311
308
|
}
|
|
312
309
|
validatePlanInputs(core, desired, current, isStateful) {
|
package/package.json
CHANGED
|
@@ -448,4 +448,47 @@ describe('Plugin tests', () => {
|
|
|
448
448
|
|
|
449
449
|
console.log(match)
|
|
450
450
|
})
|
|
451
|
+
|
|
452
|
+
it('Validates that a config correctly allows multiple of a config when allowMultiple is true', async () => {
|
|
453
|
+
const resource = spy(new class extends TestResource {
|
|
454
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
455
|
+
return {
|
|
456
|
+
id: 'ssh-config',
|
|
457
|
+
allowMultiple: true
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
const testPlugin = Plugin.create('testPlugin', [resource as any]);
|
|
463
|
+
|
|
464
|
+
const validate1 = await testPlugin.validate({
|
|
465
|
+
configs: [{ core: { type: 'ssh-config' }, parameters: { propA: 'a' } }, {
|
|
466
|
+
core: { type: 'ssh-config' },
|
|
467
|
+
parameters: { propB: 'b' }
|
|
468
|
+
}]
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
expect(validate1.resourceValidations.every((r) => r.isValid)).to.be.true;
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
it('Validates that a config correctly dis-allows multiple of a config when allowMultiple is false', async () => {
|
|
475
|
+
const resource = spy(new class extends TestResource {
|
|
476
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
477
|
+
return {
|
|
478
|
+
id: 'ssh-config',
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
const testPlugin = Plugin.create('testPlugin', [resource as any]);
|
|
484
|
+
|
|
485
|
+
const validate1 = await testPlugin.validate({
|
|
486
|
+
configs: [{ core: { type: 'ssh-config' }, parameters: { propA: 'a' } }, {
|
|
487
|
+
core: { type: 'ssh-config' },
|
|
488
|
+
parameters: { propB: 'b' }
|
|
489
|
+
}]
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
expect(validate1.resourceValidations.every((r) => r.isValid)).to.be.false;
|
|
493
|
+
})
|
|
451
494
|
});
|
package/src/plugin/plugin.ts
CHANGED
|
@@ -145,7 +145,7 @@ export class Plugin {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
async validate(data: ValidateRequestData): Promise<ValidateResponseData> {
|
|
148
|
-
const validationResults = [];
|
|
148
|
+
const validationResults: ValidateResponseData['resourceValidations'] = [];
|
|
149
149
|
for (const config of data.configs) {
|
|
150
150
|
const { core, parameters } = config;
|
|
151
151
|
|
|
@@ -160,6 +160,32 @@ export class Plugin {
|
|
|
160
160
|
validationResults.push(validation);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
// Validate that if allow multiple is false, then only 1 of each resource exists
|
|
164
|
+
const countMap = data.configs.reduce((map, resource) => {
|
|
165
|
+
if (!map.has(resource.core.type)) {
|
|
166
|
+
map.set(resource.core.type, 0);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const count = map.get(resource.core.type)!;
|
|
170
|
+
map.set(resource.core.type, count + 1)
|
|
171
|
+
|
|
172
|
+
return map;
|
|
173
|
+
}, new Map<string, number>())
|
|
174
|
+
|
|
175
|
+
const invalidMultipleConfigs = [...countMap.entries()].filter(([k, v]) => {
|
|
176
|
+
const controller = this.resourceControllers.get(k)!;
|
|
177
|
+
return !controller.parsedSettings.allowMultiple && v > 1;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (invalidMultipleConfigs.length > 0) {
|
|
181
|
+
validationResults.push(...invalidMultipleConfigs.map(([k, v]) => ({
|
|
182
|
+
resourceType: k,
|
|
183
|
+
schemaValidationErrors: [],
|
|
184
|
+
customValidationErrorMessage: `Multiple of resource type: ${k} found in configs. Only allowed 1.`,
|
|
185
|
+
isValid: false,
|
|
186
|
+
})));
|
|
187
|
+
}
|
|
188
|
+
|
|
163
189
|
await this.crossValidateResources(data.configs);
|
|
164
190
|
return {
|
|
165
191
|
resourceValidations: validationResults
|
|
@@ -188,10 +188,11 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
if (Object.entries(this.parameterSettings).some(([k, v]) =>
|
|
192
|
+
v.type === 'stateful'
|
|
193
|
+
&& typeof this.settings.allowMultiple === 'object' && this.settings.allowMultiple?.identifyingParameters?.includes(k))) {
|
|
194
|
+
throw new Error(`Resource: ${this.id}. Stateful parameters are not allowed to be identifying parameters for allowMultiple.`)
|
|
195
|
+
}
|
|
195
196
|
|
|
196
197
|
const schema = this.settings.schema as JSONSchemaType<any>;
|
|
197
198
|
if (!this.settings.importAndDestroy && (schema?.oneOf
|
|
@@ -130,7 +130,6 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
130
130
|
// Short circuit here. If the resource is non-existent, there's no point checking stateful parameters
|
|
131
131
|
if (currentArray === null
|
|
132
132
|
|| currentArray === undefined
|
|
133
|
-
|| this.settings.allowMultiple // Stateful parameters are not supported currently if allowMultiple is true
|
|
134
133
|
|| currentArray.length === 0
|
|
135
134
|
|| currentArray.filter(Boolean).length === 0
|
|
136
135
|
) {
|
|
@@ -144,13 +143,13 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
144
143
|
});
|
|
145
144
|
}
|
|
146
145
|
|
|
147
|
-
// Refresh stateful parameters. These parameters have state external to the resource.
|
|
148
|
-
//
|
|
149
|
-
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters, allParameters);
|
|
146
|
+
// Refresh stateful parameters. These parameters have state external to the resource. Each variation of the
|
|
147
|
+
// current parameters (each array element) is passed into the stateful parameter refresh.
|
|
148
|
+
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters, currentArray, allParameters);
|
|
150
149
|
|
|
151
150
|
return Plan.calculate({
|
|
152
151
|
desired,
|
|
153
|
-
currentArray:
|
|
152
|
+
currentArray: currentArray.map((c, idx) => ({ ...c, ...statefulCurrentParameters[idx] })),
|
|
154
153
|
state,
|
|
155
154
|
core,
|
|
156
155
|
settings: this.parsedSettings,
|
|
@@ -241,6 +240,7 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
241
240
|
// Parse data from the user supplied config
|
|
242
241
|
const parsedConfig = new ConfigParser(parametersToRefresh, null, this.parsedSettings.statefulParameters)
|
|
243
242
|
const {
|
|
243
|
+
allParameters,
|
|
244
244
|
allNonStatefulParameters,
|
|
245
245
|
allStatefulParameters,
|
|
246
246
|
} = parsedConfig;
|
|
@@ -249,26 +249,21 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
249
249
|
|
|
250
250
|
if (currentParametersArray === null
|
|
251
251
|
|| currentParametersArray === undefined
|
|
252
|
-
|| this.settings.allowMultiple // Stateful parameters are not supported currently if allowMultiple is true
|
|
253
252
|
|| currentParametersArray.filter(Boolean).length === 0
|
|
254
253
|
) {
|
|
255
|
-
|
|
256
|
-
await this.applyTransformParameters(result, true);
|
|
257
|
-
this.removeDefaultValues(result, parameters)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return currentParametersArray
|
|
261
|
-
?.map((r) => ({ core, parameters: r }))
|
|
262
|
-
?? null;
|
|
254
|
+
return [];
|
|
263
255
|
}
|
|
264
256
|
|
|
265
|
-
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters,
|
|
266
|
-
const
|
|
257
|
+
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters, currentParametersArray, allParameters);
|
|
258
|
+
const resultParametersArray = currentParametersArray
|
|
259
|
+
?.map((r, idx) => ({ ...r, ...statefulCurrentParameters[idx] }))
|
|
267
260
|
|
|
268
|
-
|
|
269
|
-
|
|
261
|
+
for (const result of resultParametersArray) {
|
|
262
|
+
await this.applyTransformParameters(result, true);
|
|
263
|
+
this.removeDefaultValues(result, parameters);
|
|
264
|
+
}
|
|
270
265
|
|
|
271
|
-
return
|
|
266
|
+
return resultParametersArray?.map((r) => ({ core, parameters: r }))
|
|
272
267
|
}
|
|
273
268
|
|
|
274
269
|
private async applyCreate(plan: Plan<T>): Promise<void> {
|
|
@@ -410,21 +405,27 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
410
405
|
|
|
411
406
|
// Refresh stateful parameters
|
|
412
407
|
// This refreshes parameters that are stateful (they can be added, deleted separately from the resource)
|
|
413
|
-
private async refreshStatefulParameters(
|
|
414
|
-
|
|
408
|
+
private async refreshStatefulParameters(
|
|
409
|
+
statefulParametersConfig: Partial<T>,
|
|
410
|
+
currentArray: Array<Partial<T>>,
|
|
411
|
+
allParameters: Partial<T>
|
|
412
|
+
): Promise<Array<Partial<T>>> {
|
|
413
|
+
const result: Array<Partial<T>> = Array.from({ length: currentArray.length }, () => ({}))
|
|
415
414
|
const sortedEntries = Object.entries(statefulParametersConfig)
|
|
416
415
|
.sort(
|
|
417
416
|
([key1], [key2]) => this.parsedSettings.statefulParameterOrder.get(key1)! - this.parsedSettings.statefulParameterOrder.get(key2)!
|
|
418
417
|
)
|
|
419
418
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
419
|
+
for (const [idx, refreshedParams] of currentArray.entries()) {
|
|
420
|
+
await Promise.all(sortedEntries.map(async ([key, desiredValue]) => {
|
|
421
|
+
const statefulParameter = this.parsedSettings.statefulParameters.get(key);
|
|
422
|
+
if (!statefulParameter) {
|
|
423
|
+
throw new Error(`Stateful parameter ${key} was not found`);
|
|
424
|
+
}
|
|
425
425
|
|
|
426
|
-
|
|
427
|
-
|
|
426
|
+
(result[idx][key] as T[keyof T] | null) = await statefulParameter.refresh(desiredValue ?? null, { ...allParameters, ...refreshedParams })
|
|
427
|
+
}))
|
|
428
|
+
}
|
|
428
429
|
|
|
429
430
|
return result;
|
|
430
431
|
}
|