codify-plugin-lib 1.0.147 → 1.0.148
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 +24 -27
- 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 +24 -29
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)
|
|
@@ -79,13 +79,12 @@ export class ResourceController {
|
|
|
79
79
|
await this.applyTransformParameters(state);
|
|
80
80
|
// Parse data from the user supplied config
|
|
81
81
|
const parsedConfig = new ConfigParser(desired, state, this.parsedSettings.statefulParameters);
|
|
82
|
-
const {
|
|
82
|
+
const { allNonStatefulParameters, allStatefulParameters, } = parsedConfig;
|
|
83
83
|
// Refresh resource parameters. This refreshes the parameters that configure the resource itself
|
|
84
84
|
const currentArray = await this.refreshNonStatefulParameters(allNonStatefulParameters);
|
|
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,
|
|
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);
|
|
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,
|
|
@@ -170,21 +169,17 @@ export class ResourceController {
|
|
|
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);
|
|
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);
|
|
@@ -297,16 +292,18 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
297
292
|
// Refresh stateful parameters
|
|
298
293
|
// This refreshes parameters that are stateful (they can be added, deleted separately from the resource)
|
|
299
294
|
async refreshStatefulParameters(statefulParametersConfig, allParameters) {
|
|
300
|
-
const result = {};
|
|
295
|
+
const result = Array.from({ length: allParameters.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 allParameters.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, 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
|
|
@@ -119,7 +119,6 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
119
119
|
// Parse data from the user supplied config
|
|
120
120
|
const parsedConfig = new ConfigParser(desired, state, this.parsedSettings.statefulParameters)
|
|
121
121
|
const {
|
|
122
|
-
allParameters,
|
|
123
122
|
allNonStatefulParameters,
|
|
124
123
|
allStatefulParameters,
|
|
125
124
|
} = parsedConfig;
|
|
@@ -130,7 +129,6 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
130
129
|
// Short circuit here. If the resource is non-existent, there's no point checking stateful parameters
|
|
131
130
|
if (currentArray === null
|
|
132
131
|
|| currentArray === undefined
|
|
133
|
-
|| this.settings.allowMultiple // Stateful parameters are not supported currently if allowMultiple is true
|
|
134
132
|
|| currentArray.length === 0
|
|
135
133
|
|| currentArray.filter(Boolean).length === 0
|
|
136
134
|
) {
|
|
@@ -144,13 +142,13 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
144
142
|
});
|
|
145
143
|
}
|
|
146
144
|
|
|
147
|
-
// Refresh stateful parameters. These parameters have state external to the resource.
|
|
148
|
-
//
|
|
149
|
-
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters,
|
|
145
|
+
// Refresh stateful parameters. These parameters have state external to the resource. Each variation of the
|
|
146
|
+
// current parameters (each array element) is passed into the stateful parameter refresh.
|
|
147
|
+
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters, currentArray);
|
|
150
148
|
|
|
151
149
|
return Plan.calculate({
|
|
152
150
|
desired,
|
|
153
|
-
currentArray:
|
|
151
|
+
currentArray: currentArray.map((c, idx) => ({ ...c, ...statefulCurrentParameters[idx] })),
|
|
154
152
|
state,
|
|
155
153
|
core,
|
|
156
154
|
settings: this.parsedSettings,
|
|
@@ -249,26 +247,21 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
249
247
|
|
|
250
248
|
if (currentParametersArray === null
|
|
251
249
|
|| currentParametersArray === undefined
|
|
252
|
-
|| this.settings.allowMultiple // Stateful parameters are not supported currently if allowMultiple is true
|
|
253
250
|
|| currentParametersArray.filter(Boolean).length === 0
|
|
254
251
|
) {
|
|
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;
|
|
252
|
+
return [];
|
|
263
253
|
}
|
|
264
254
|
|
|
265
|
-
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters,
|
|
266
|
-
const
|
|
255
|
+
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters, currentParametersArray);
|
|
256
|
+
const resultParametersArray = currentParametersArray
|
|
257
|
+
?.map((r, idx) => ({ ...r, ...statefulCurrentParameters[idx] }))
|
|
267
258
|
|
|
268
|
-
|
|
269
|
-
|
|
259
|
+
for (const result of resultParametersArray) {
|
|
260
|
+
await this.applyTransformParameters(result, true);
|
|
261
|
+
this.removeDefaultValues(result, parameters);
|
|
262
|
+
}
|
|
270
263
|
|
|
271
|
-
return
|
|
264
|
+
return resultParametersArray?.map((r) => ({ core, parameters: r }))
|
|
272
265
|
}
|
|
273
266
|
|
|
274
267
|
private async applyCreate(plan: Plan<T>): Promise<void> {
|
|
@@ -410,21 +403,23 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
410
403
|
|
|
411
404
|
// Refresh stateful parameters
|
|
412
405
|
// This refreshes parameters that are stateful (they can be added, deleted separately from the resource)
|
|
413
|
-
private async refreshStatefulParameters(statefulParametersConfig: Partial<T>, allParameters: Partial<T
|
|
414
|
-
const result: Partial<T
|
|
406
|
+
private async refreshStatefulParameters(statefulParametersConfig: Partial<T>, allParameters: Array<Partial<T>>): Promise<Array<Partial<T>>> {
|
|
407
|
+
const result: Array<Partial<T>> = Array.from({ length: allParameters.length }, () => ({}))
|
|
415
408
|
const sortedEntries = Object.entries(statefulParametersConfig)
|
|
416
409
|
.sort(
|
|
417
410
|
([key1], [key2]) => this.parsedSettings.statefulParameterOrder.get(key1)! - this.parsedSettings.statefulParameterOrder.get(key2)!
|
|
418
411
|
)
|
|
419
412
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
413
|
+
for (const [idx, refreshedParams] of allParameters.entries()) {
|
|
414
|
+
await Promise.all(sortedEntries.map(async ([key, desiredValue]) => {
|
|
415
|
+
const statefulParameter = this.parsedSettings.statefulParameters.get(key);
|
|
416
|
+
if (!statefulParameter) {
|
|
417
|
+
throw new Error(`Stateful parameter ${key} was not found`);
|
|
418
|
+
}
|
|
425
419
|
|
|
426
|
-
|
|
427
|
-
|
|
420
|
+
(result[idx][key] as T[keyof T] | null) = await statefulParameter.refresh(desiredValue ?? null, refreshedParams)
|
|
421
|
+
}))
|
|
422
|
+
}
|
|
428
423
|
|
|
429
424
|
return result;
|
|
430
425
|
}
|