codify-plugin-lib 1.0.146 → 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 -23
- 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 -24
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,17 +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
|
-
return
|
|
176
|
-
|
|
177
|
-
|
|
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);
|
|
178
181
|
}
|
|
179
|
-
|
|
180
|
-
const resultParameters = { ...currentParametersArray[0], ...statefulCurrentParameters };
|
|
181
|
-
await this.applyTransformParameters(resultParameters, true);
|
|
182
|
-
this.removeDefaultValues(resultParameters, parameters);
|
|
183
|
-
return [{ core, parameters: resultParameters }];
|
|
182
|
+
return resultParametersArray?.map((r) => ({ core, parameters: r }));
|
|
184
183
|
}
|
|
185
184
|
async applyCreate(plan) {
|
|
186
185
|
await this.resource.create(plan);
|
|
@@ -293,16 +292,18 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
293
292
|
// Refresh stateful parameters
|
|
294
293
|
// This refreshes parameters that are stateful (they can be added, deleted separately from the resource)
|
|
295
294
|
async refreshStatefulParameters(statefulParametersConfig, allParameters) {
|
|
296
|
-
const result = {};
|
|
295
|
+
const result = Array.from({ length: allParameters.length }, () => ({}));
|
|
297
296
|
const sortedEntries = Object.entries(statefulParametersConfig)
|
|
298
297
|
.sort(([key1], [key2]) => this.parsedSettings.statefulParameterOrder.get(key1) - this.parsedSettings.statefulParameterOrder.get(key2));
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
+
}
|
|
306
307
|
return result;
|
|
307
308
|
}
|
|
308
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,21 +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
|
-
return
|
|
256
|
-
?.map((r) => ({ core, parameters: r }))
|
|
257
|
-
?? null;
|
|
252
|
+
return [];
|
|
258
253
|
}
|
|
259
254
|
|
|
260
|
-
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters,
|
|
261
|
-
const
|
|
255
|
+
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters, currentParametersArray);
|
|
256
|
+
const resultParametersArray = currentParametersArray
|
|
257
|
+
?.map((r, idx) => ({ ...r, ...statefulCurrentParameters[idx] }))
|
|
262
258
|
|
|
263
|
-
|
|
264
|
-
|
|
259
|
+
for (const result of resultParametersArray) {
|
|
260
|
+
await this.applyTransformParameters(result, true);
|
|
261
|
+
this.removeDefaultValues(result, parameters);
|
|
262
|
+
}
|
|
265
263
|
|
|
266
|
-
return
|
|
264
|
+
return resultParametersArray?.map((r) => ({ core, parameters: r }))
|
|
267
265
|
}
|
|
268
266
|
|
|
269
267
|
private async applyCreate(plan: Plan<T>): Promise<void> {
|
|
@@ -405,21 +403,23 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
405
403
|
|
|
406
404
|
// Refresh stateful parameters
|
|
407
405
|
// This refreshes parameters that are stateful (they can be added, deleted separately from the resource)
|
|
408
|
-
private async refreshStatefulParameters(statefulParametersConfig: Partial<T>, allParameters: Partial<T
|
|
409
|
-
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 }, () => ({}))
|
|
410
408
|
const sortedEntries = Object.entries(statefulParametersConfig)
|
|
411
409
|
.sort(
|
|
412
410
|
([key1], [key2]) => this.parsedSettings.statefulParameterOrder.get(key1)! - this.parsedSettings.statefulParameterOrder.get(key2)!
|
|
413
411
|
)
|
|
414
412
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
+
}
|
|
420
419
|
|
|
421
|
-
|
|
422
|
-
|
|
420
|
+
(result[idx][key] as T[keyof T] | null) = await statefulParameter.refresh(desiredValue ?? null, refreshedParams)
|
|
421
|
+
}))
|
|
422
|
+
}
|
|
423
423
|
|
|
424
424
|
return result;
|
|
425
425
|
}
|