codify-plugin-lib 1.0.49 → 1.0.51
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/entities/change-set.d.ts +3 -3
- package/dist/entities/change-set.js +11 -11
- package/dist/entities/plan-types.d.ts +3 -3
- package/dist/entities/plan.d.ts +2 -2
- package/dist/entities/plan.js +6 -6
- package/dist/entities/plugin.js +1 -1
- package/dist/entities/resource-options.d.ts +31 -0
- package/dist/entities/resource-options.js +78 -0
- package/dist/entities/resource-types.d.ts +2 -10
- package/dist/entities/resource.d.ts +20 -9
- package/dist/entities/resource.js +149 -82
- package/dist/entities/stateful-parameter.d.ts +6 -6
- package/dist/entities/stateful-parameter.js +13 -13
- package/dist/entities/transform-parameter.d.ts +4 -0
- package/dist/entities/transform-parameter.js +2 -0
- package/package.json +1 -1
- package/src/entities/change-set.ts +13 -13
- package/src/entities/plan-types.ts +3 -3
- package/src/entities/plan.test.ts +1 -1
- package/src/entities/plan.ts +7 -7
- package/src/entities/plugin.ts +1 -1
- package/src/entities/resource-options.ts +156 -0
- package/src/entities/resource-parameters.test.ts +265 -10
- package/src/entities/resource-types.ts +2 -14
- package/src/entities/resource.test.ts +16 -20
- package/src/entities/resource.ts +206 -117
- package/src/entities/stateful-parameter.test.ts +5 -5
- package/src/entities/stateful-parameter.ts +15 -15
- package/src/entities/transform-parameter.ts +7 -0
package/src/entities/resource.ts
CHANGED
|
@@ -2,9 +2,13 @@ import { ParameterOperation, ResourceConfig, ResourceOperation, StringIndexedObj
|
|
|
2
2
|
import { ParameterChange } from './change-set.js';
|
|
3
3
|
import { Plan } from './plan.js';
|
|
4
4
|
import { StatefulParameter } from './stateful-parameter.js';
|
|
5
|
-
import {
|
|
5
|
+
import { ResourceParameterOptions, ValidationResult } from './resource-types.js';
|
|
6
6
|
import { setsEqual, splitUserConfig } from '../utils/utils.js';
|
|
7
|
-
import {
|
|
7
|
+
import { ParameterOptions, PlanOptions } from './plan-types.js';
|
|
8
|
+
import { TransformParameter } from './transform-parameter.js';
|
|
9
|
+
import { ResourceOptions, ResourceOptionsParser } from './resource-options.js';
|
|
10
|
+
import Ajv from 'ajv';
|
|
11
|
+
import Ajv2020, { ValidateFunction } from 'ajv/dist/2020.js';
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
14
|
* Description of resource here
|
|
@@ -14,92 +18,99 @@ import { ParameterConfiguration, PlanConfiguration } from './plan-types.js';
|
|
|
14
18
|
*
|
|
15
19
|
*/
|
|
16
20
|
export abstract class Resource<T extends StringIndexedObject> {
|
|
17
|
-
|
|
18
21
|
readonly typeId: string;
|
|
19
22
|
readonly statefulParameters: Map<keyof T, StatefulParameter<T, T[keyof T]>>;
|
|
23
|
+
readonly transformParameters: Map<keyof T, TransformParameter<T>>
|
|
24
|
+
readonly resourceParameters: Map<keyof T, ResourceParameterOptions>;
|
|
25
|
+
|
|
26
|
+
readonly statefulParameterOrder: Map<keyof T, number>;
|
|
27
|
+
readonly transformParameterOrder: Map<keyof T, number>;
|
|
28
|
+
|
|
20
29
|
readonly dependencies: string[]; // TODO: Change this to a string
|
|
21
|
-
readonly
|
|
22
|
-
readonly
|
|
30
|
+
readonly parameterOptions: Record<keyof T, ParameterOptions>
|
|
31
|
+
readonly options: ResourceOptions<T>;
|
|
23
32
|
readonly defaultValues: Partial<Record<keyof T, unknown>>;
|
|
24
33
|
|
|
25
|
-
protected
|
|
26
|
-
|
|
34
|
+
protected ajv?: Ajv.default;
|
|
35
|
+
protected schemaValidator?: ValidateFunction;
|
|
36
|
+
|
|
37
|
+
protected constructor(options: ResourceOptions<T>) {
|
|
38
|
+
this.typeId = options.type;
|
|
39
|
+
this.dependencies = options.dependencies ?? [];
|
|
40
|
+
this.options = options;
|
|
27
41
|
|
|
28
|
-
this.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
if (this.options.schema) {
|
|
43
|
+
this.ajv = new Ajv2020.default({
|
|
44
|
+
strict: true,
|
|
45
|
+
strictRequired: false,
|
|
46
|
+
})
|
|
47
|
+
this.schemaValidator = this.ajv.compile(this.options.schema);
|
|
48
|
+
}
|
|
32
49
|
|
|
33
|
-
|
|
34
|
-
this.
|
|
50
|
+
const parser = new ResourceOptionsParser<T>(options);
|
|
51
|
+
this.statefulParameters = parser.statefulParameters;
|
|
52
|
+
this.transformParameters = parser.transformParameters;
|
|
53
|
+
this.resourceParameters = parser.resourceParameters;
|
|
54
|
+
this.parameterOptions = parser.changeSetParameterOptions;
|
|
55
|
+
this.defaultValues = parser.defaultValues;
|
|
56
|
+
this.statefulParameterOrder = parser.statefulParameterOrder;
|
|
57
|
+
this.transformParameterOrder = parser.transformParameterOrder;
|
|
35
58
|
}
|
|
36
59
|
|
|
37
60
|
async onInitialize(): Promise<void> {}
|
|
38
61
|
|
|
62
|
+
async validateResource(parameters: unknown): Promise<ValidationResult> {
|
|
63
|
+
if (this.schemaValidator) {
|
|
64
|
+
const isValid = this.schemaValidator(parameters);
|
|
65
|
+
|
|
66
|
+
if (!isValid) {
|
|
67
|
+
return {
|
|
68
|
+
isValid: false,
|
|
69
|
+
errors: this.schemaValidator?.errors ?? [],
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return this.validate(parameters);
|
|
75
|
+
}
|
|
76
|
+
|
|
39
77
|
// TODO: Add state in later.
|
|
40
78
|
// Currently only calculating how to add things to reach desired state. Can't delete resources.
|
|
41
79
|
// Add previousConfig as a parameter for plan(desired, previous);
|
|
42
80
|
async plan(desiredConfig: Partial<T> & ResourceConfig): Promise<Plan<T>> {
|
|
43
|
-
|
|
44
|
-
// Explanation: these are settings for how the plan will be generated
|
|
45
|
-
const planConfiguration: PlanConfiguration<T> = {
|
|
81
|
+
const planOptions: PlanOptions<T> = {
|
|
46
82
|
statefulMode: false,
|
|
47
|
-
|
|
83
|
+
parameterOptions: this.parameterOptions,
|
|
48
84
|
}
|
|
49
85
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
86
|
+
// Parse data from the user supplied config
|
|
87
|
+
const parsedConfig = new ConfigParser(desiredConfig, this.statefulParameters, this.transformParameters)
|
|
88
|
+
const {
|
|
89
|
+
parameters: desiredParameters,
|
|
90
|
+
resourceMetadata,
|
|
91
|
+
resourceParameters,
|
|
92
|
+
statefulParameters
|
|
93
|
+
} = parsedConfig;
|
|
53
94
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
]) as Partial<T>;
|
|
95
|
+
this.addDefaultValues(resourceParameters);
|
|
96
|
+
await this.applyTransformParameters(resourceParameters);
|
|
57
97
|
|
|
58
|
-
|
|
59
|
-
|
|
98
|
+
// Refresh resource parameters. This refreshes the parameters that configure the resource itself
|
|
99
|
+
const currentParameters = await this.refreshResourceParameters(resourceParameters);
|
|
60
100
|
|
|
61
|
-
//
|
|
62
|
-
// This refreshes the parameters that configure the resource itself
|
|
63
|
-
const entriesToRefresh = new Map(Object.entries(resourceParameters));
|
|
64
|
-
const currentParameters = await this.refresh(entriesToRefresh);
|
|
65
|
-
|
|
66
|
-
// Short circuit here. If resource is non-existent, then there's no point checking stateful parameters
|
|
101
|
+
// Short circuit here. If the resource is non-existent, there's no point checking stateful parameters
|
|
67
102
|
if (currentParameters == null) {
|
|
68
|
-
return Plan.create(desiredParameters, null, resourceMetadata,
|
|
103
|
+
return Plan.create(desiredParameters, null, resourceMetadata, planOptions);
|
|
69
104
|
}
|
|
70
105
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Refresh stateful parameters
|
|
74
|
-
// This refreshes parameters that are stateful (they can be added, deleted separately from the resource)
|
|
75
|
-
for(const statefulParameter of statefulParameters) {
|
|
76
|
-
const desiredValue = desiredParameters[statefulParameter.name];
|
|
77
|
-
|
|
78
|
-
let currentValue = await statefulParameter.refresh(desiredValue ?? null) ?? undefined;
|
|
79
|
-
|
|
80
|
-
// In stateless mode, filter the refreshed parameters by the desired to ensure that no deletes happen
|
|
81
|
-
if (Array.isArray(currentValue)
|
|
82
|
-
&& Array.isArray(desiredValue)
|
|
83
|
-
&& !planConfiguration.statefulMode
|
|
84
|
-
&& !statefulParameter.configuration.disableStatelessModeArrayFiltering
|
|
85
|
-
) {
|
|
86
|
-
currentValue = currentValue.filter((c) => desiredValue?.some((d) => {
|
|
87
|
-
const pc = planConfiguration?.parameterConfigurations?.[statefulParameter.name];
|
|
88
|
-
if (pc && pc.isElementEqual) {
|
|
89
|
-
return pc.isElementEqual(d, c);
|
|
90
|
-
}
|
|
91
|
-
return d === c;
|
|
92
|
-
})) as any;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
currentParameters[statefulParameter.name] = currentValue;
|
|
96
|
-
}
|
|
106
|
+
// Refresh stateful parameters. These parameters have state external to the resource
|
|
107
|
+
const statefulCurrentParameters = await this.refreshStatefulParameters(statefulParameters, planOptions.statefulMode);
|
|
97
108
|
|
|
98
109
|
return Plan.create(
|
|
99
|
-
|
|
100
|
-
currentParameters as Partial<T>,
|
|
110
|
+
{ ...resourceParameters, ...statefulParameters },
|
|
111
|
+
{ ...currentParameters, ...statefulCurrentParameters } as Partial<T>,
|
|
101
112
|
resourceMetadata,
|
|
102
|
-
|
|
113
|
+
planOptions,
|
|
103
114
|
)
|
|
104
115
|
}
|
|
105
116
|
|
|
@@ -130,6 +141,8 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
130
141
|
|
|
131
142
|
const statefulParameterChanges = plan.changeSet.parameterChanges
|
|
132
143
|
.filter((pc: ParameterChange<T>) => this.statefulParameters.has(pc.name))
|
|
144
|
+
.sort((a, b) => this.statefulParameterOrder.get(a.name)! - this.statefulParameterOrder.get(b.name)!)
|
|
145
|
+
|
|
133
146
|
for (const parameterChange of statefulParameterChanges) {
|
|
134
147
|
const statefulParameter = this.statefulParameters.get(parameterChange.name)!;
|
|
135
148
|
await statefulParameter.applyAdd(parameterChange.newValue, plan);
|
|
@@ -144,6 +157,7 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
144
157
|
|
|
145
158
|
const statelessParameterChanges = parameterChanges
|
|
146
159
|
.filter((pc: ParameterChange<T>) => !this.statefulParameters.has(pc.name))
|
|
160
|
+
|
|
147
161
|
for (const pc of statelessParameterChanges) {
|
|
148
162
|
// TODO: When stateful mode is added in the future. Dynamically choose if deletes are allowed
|
|
149
163
|
await this.applyModify(pc.name, pc.newValue, pc.previousValue, false, plan);
|
|
@@ -151,6 +165,8 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
151
165
|
|
|
152
166
|
const statefulParameterChanges = parameterChanges
|
|
153
167
|
.filter((pc: ParameterChange<T>) => this.statefulParameters.has(pc.name))
|
|
168
|
+
.sort((a, b) => this.statefulParameterOrder.get(a.name)! - this.statefulParameterOrder.get(b.name)!)
|
|
169
|
+
|
|
154
170
|
for (const parameterChange of statefulParameterChanges) {
|
|
155
171
|
const statefulParameter = this.statefulParameters.get(parameterChange.name)!;
|
|
156
172
|
|
|
@@ -175,9 +191,11 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
175
191
|
private async _applyDestroy(plan: Plan<T>): Promise<void> {
|
|
176
192
|
// If this option is set (defaults to false), then stateful parameters need to be destroyed
|
|
177
193
|
// as well. This means that the stateful parameter wouldn't have been normally destroyed with applyDestroy()
|
|
178
|
-
if (this.
|
|
194
|
+
if (this.options.callStatefulParameterRemoveOnDestroy) {
|
|
179
195
|
const statefulParameterChanges = plan.changeSet.parameterChanges
|
|
180
196
|
.filter((pc: ParameterChange<T>) => this.statefulParameters.has(pc.name))
|
|
197
|
+
.sort((a, b) => this.statefulParameterOrder.get(a.name)! - this.statefulParameterOrder.get(b.name)!)
|
|
198
|
+
|
|
181
199
|
for (const parameterChange of statefulParameterChanges) {
|
|
182
200
|
const statefulParameter = this.statefulParameters.get(parameterChange.name)!;
|
|
183
201
|
await statefulParameter.applyRemove(parameterChange.previousValue, plan);
|
|
@@ -187,59 +205,6 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
187
205
|
await this.applyDestroy(plan);
|
|
188
206
|
}
|
|
189
207
|
|
|
190
|
-
private initializeParameterConfigurations(
|
|
191
|
-
resourceConfiguration: ResourceConfiguration<T>
|
|
192
|
-
): Record<keyof T, ParameterConfiguration> {
|
|
193
|
-
const resourceParameters = Object.fromEntries(
|
|
194
|
-
Object.entries(resourceConfiguration.parameterConfigurations ?? {})
|
|
195
|
-
?.map(([name, value]) => ([name, { ...value, isStatefulParameter: false }]))
|
|
196
|
-
) as Record<keyof T, ParameterConfiguration>
|
|
197
|
-
|
|
198
|
-
const statefulParameters = resourceConfiguration.statefulParameters
|
|
199
|
-
?.reduce((obj, sp) => {
|
|
200
|
-
return {
|
|
201
|
-
...obj,
|
|
202
|
-
[sp.name]: {
|
|
203
|
-
...sp.configuration,
|
|
204
|
-
isStatefulParameter: true,
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}, {}) ?? {}
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
...resourceParameters,
|
|
211
|
-
...statefulParameters,
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
private initializeDefaultValues(
|
|
217
|
-
resourceConfiguration: ResourceConfiguration<T>
|
|
218
|
-
): Partial<Record<keyof T, unknown>> {
|
|
219
|
-
if (!resourceConfiguration.parameterConfigurations) {
|
|
220
|
-
return {};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return Object.fromEntries(
|
|
224
|
-
Object.entries(resourceConfiguration.parameterConfigurations)
|
|
225
|
-
.filter((p) => p[1]?.defaultValue !== undefined)
|
|
226
|
-
.map((config) => [config[0], config[1]!.defaultValue])
|
|
227
|
-
) as Partial<Record<keyof T, unknown>>;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
private validateResourceConfiguration(data: ResourceConfiguration<T>) {
|
|
231
|
-
// Stateful parameters are configured within the object not in the resource.
|
|
232
|
-
if (data.parameterConfigurations && data.statefulParameters) {
|
|
233
|
-
const parameters = [...Object.keys(data.parameterConfigurations)];
|
|
234
|
-
const statefulParameterSet = new Set(data.statefulParameters.map((sp) => sp.name));
|
|
235
|
-
|
|
236
|
-
const intersection = parameters.some((p) => statefulParameterSet.has(p));
|
|
237
|
-
if (intersection) {
|
|
238
|
-
throw new Error(`Resource ${this.typeId} cannot declare a parameter as both stateful and non-stateful`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
208
|
private validateRefreshResults(refresh: Partial<T> | null, desiredMap: Map<keyof T, T[keyof T]>) {
|
|
244
209
|
if (!refresh) {
|
|
245
210
|
return;
|
|
@@ -250,7 +215,7 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
250
215
|
|
|
251
216
|
if (!setsEqual(desiredKeys, refreshKeys)) {
|
|
252
217
|
throw new Error(
|
|
253
|
-
`Resource ${this.
|
|
218
|
+
`Resource ${this.typeId}
|
|
254
219
|
refresh() must return back exactly the keys that were provided
|
|
255
220
|
Missing: ${[...desiredKeys].filter((k) => !refreshKeys.has(k))};
|
|
256
221
|
Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
@@ -258,6 +223,28 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
|
258
223
|
}
|
|
259
224
|
}
|
|
260
225
|
|
|
226
|
+
private async applyTransformParameters(desired: Partial<T>): Promise<void> {
|
|
227
|
+
const orderedEntries = [...this.transformParameters.entries()]
|
|
228
|
+
.sort(([keyA], [keyB]) => this.transformParameterOrder.get(keyA)! - this.transformParameterOrder.get(keyB)!)
|
|
229
|
+
|
|
230
|
+
for (const [key, tp] of orderedEntries) {
|
|
231
|
+
if (desired[key] !== null) {
|
|
232
|
+
const transformedValue = await tp.transform(desired[key]);
|
|
233
|
+
|
|
234
|
+
if (Object.keys(transformedValue).some((k) => desired[k] !== undefined)) {
|
|
235
|
+
throw new Error(`Transform parameter ${key as string} is attempting to override existing value ${desired[key]}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
Object.entries(transformedValue).forEach(([tvKey, tvValue]) => {
|
|
239
|
+
// @ts-ignore
|
|
240
|
+
desired[tvKey] = tvValue;
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
delete desired[key];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
261
248
|
private addDefaultValues(desired: Partial<T>): void {
|
|
262
249
|
Object.entries(this.defaultValues)
|
|
263
250
|
.forEach(([key, defaultValue]) => {
|
|
@@ -268,7 +255,59 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
|
268
255
|
});
|
|
269
256
|
}
|
|
270
257
|
|
|
271
|
-
|
|
258
|
+
private async refreshResourceParameters(resourceParameters: Partial<T>): Promise<Partial<T> | null> {
|
|
259
|
+
const entriesToRefresh = new Map(Object.entries(resourceParameters));
|
|
260
|
+
const currentParameters = await this.refresh(entriesToRefresh);
|
|
261
|
+
|
|
262
|
+
this.validateRefreshResults(currentParameters, entriesToRefresh);
|
|
263
|
+
return currentParameters;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Refresh stateful parameters
|
|
267
|
+
// This refreshes parameters that are stateful (they can be added, deleted separately from the resource)
|
|
268
|
+
private async refreshStatefulParameters(statefulParametersConfig: Partial<T>, isStatefulMode: boolean): Promise<Partial<T>> {
|
|
269
|
+
const currentParameters: Partial<T> = {}
|
|
270
|
+
const sortedEntries = Object.entries(statefulParametersConfig)
|
|
271
|
+
.sort(([key1], [key2]) => this.statefulParameterOrder.get(key1)! - this.statefulParameterOrder.get(key2)!)
|
|
272
|
+
|
|
273
|
+
for(const [key, desiredValue] of sortedEntries) {
|
|
274
|
+
const statefulParameter = this.statefulParameters.get(key);
|
|
275
|
+
if (!statefulParameter) {
|
|
276
|
+
throw new Error(`Stateful parameter ${key} was not found`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let currentValue = await statefulParameter.refresh(desiredValue ?? null);
|
|
280
|
+
|
|
281
|
+
// In stateless mode, filter the refreshed parameters by the desired to ensure that no deletes happen
|
|
282
|
+
// Otherwise the change set will pick up the extra keys from the current and try to delete them
|
|
283
|
+
// This allows arrays within stateful parameters to be first class objects
|
|
284
|
+
if (Array.isArray(currentValue)
|
|
285
|
+
&& Array.isArray(desiredValue)
|
|
286
|
+
&& !isStatefulMode
|
|
287
|
+
&& !statefulParameter.options.disableStatelessModeArrayFiltering
|
|
288
|
+
) {
|
|
289
|
+
currentValue = currentValue.filter((c) => desiredValue?.some((d) => {
|
|
290
|
+
const parameterOptions = statefulParameter.options as any;
|
|
291
|
+
if (parameterOptions && parameterOptions.isElementEqual) {
|
|
292
|
+
return parameterOptions.isElementEqual(d, c);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return d === c;
|
|
296
|
+
})) as any;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// @ts-ignore
|
|
300
|
+
currentParameters[key] = currentValue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return currentParameters;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async validate(parameters: unknown): Promise<ValidationResult> {
|
|
307
|
+
return {
|
|
308
|
+
isValid: true,
|
|
309
|
+
}
|
|
310
|
+
};
|
|
272
311
|
|
|
273
312
|
abstract refresh(keys: Map<keyof T, T[keyof T]>): Promise<Partial<T> | null>;
|
|
274
313
|
|
|
@@ -278,3 +317,53 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
|
278
317
|
|
|
279
318
|
abstract applyDestroy(plan: Plan<T>): Promise<void>;
|
|
280
319
|
}
|
|
320
|
+
|
|
321
|
+
class ConfigParser<T extends StringIndexedObject> {
|
|
322
|
+
private config: Partial<T> & ResourceConfig;
|
|
323
|
+
private statefulParametersMap: Map<keyof T, StatefulParameter<T, T[keyof T]>>;
|
|
324
|
+
private transformParametersMap: Map<keyof T, TransformParameter<T>>;
|
|
325
|
+
|
|
326
|
+
constructor(
|
|
327
|
+
config: Partial<T> & ResourceConfig,
|
|
328
|
+
statefulParameters: Map<keyof T, StatefulParameter<T, T[keyof T]>>,
|
|
329
|
+
transformParameters: Map<keyof T, TransformParameter<T>>,
|
|
330
|
+
) {
|
|
331
|
+
this.config = config;
|
|
332
|
+
this.statefulParametersMap = statefulParameters;
|
|
333
|
+
this.transformParametersMap = transformParameters;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
get resourceMetadata(): ResourceConfig {
|
|
337
|
+
const { resourceMetadata } = splitUserConfig(this.config);
|
|
338
|
+
return resourceMetadata;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
get parameters(): Partial<T> {
|
|
342
|
+
const { parameters } = splitUserConfig(this.config);
|
|
343
|
+
return parameters;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
get resourceParameters(): Partial<T> {
|
|
347
|
+
const parameters = this.parameters;
|
|
348
|
+
|
|
349
|
+
return Object.fromEntries([
|
|
350
|
+
...Object.entries(parameters).filter(([key]) => !(this.statefulParametersMap.has(key) || this.transformParametersMap.has(key))),
|
|
351
|
+
]) as Partial<T>;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
get statefulParameters(): Partial<T> {
|
|
355
|
+
const parameters = this.parameters;
|
|
356
|
+
|
|
357
|
+
return Object.fromEntries([
|
|
358
|
+
...Object.entries(parameters).filter(([key]) => this.statefulParametersMap.has(key)),
|
|
359
|
+
]) as Partial<T>;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
get transformParameters(): Partial<T> {
|
|
363
|
+
const parameters = this.parameters;
|
|
364
|
+
|
|
365
|
+
return Object.fromEntries([
|
|
366
|
+
...Object.entries(parameters).filter(([key]) => this.transformParametersMap.has(key)),
|
|
367
|
+
]) as Partial<T>;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { ArrayStatefulParameter,
|
|
2
|
+
import { ArrayStatefulParameter, ArrayStatefulParameterOptions, } from './stateful-parameter.js';
|
|
3
3
|
import { Plan } from './plan.js';
|
|
4
4
|
import { spy } from 'sinon';
|
|
5
5
|
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
@@ -10,8 +10,8 @@ interface TestConfig {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
class TestArrayParameter extends ArrayStatefulParameter<TestConfig, string> {
|
|
13
|
-
constructor(
|
|
14
|
-
super(
|
|
13
|
+
constructor(options?: ArrayStatefulParameterOptions<TestConfig>) {
|
|
14
|
+
super(options ?? {
|
|
15
15
|
name: 'propA'
|
|
16
16
|
})
|
|
17
17
|
}
|
|
@@ -67,7 +67,7 @@ describe('Stateful parameter tests', () => {
|
|
|
67
67
|
{ propA: ['a', 'c', 'd', 'e', 'f'] }, // b to remove, d, e, f to add
|
|
68
68
|
{ propA: ['a', 'b', 'c'] },
|
|
69
69
|
{ type: 'typeA' },
|
|
70
|
-
{ statefulMode: true,
|
|
70
|
+
{ statefulMode: true, parameterOptions: { propA: { isStatefulParameter: true }} }
|
|
71
71
|
);
|
|
72
72
|
|
|
73
73
|
expect(plan.changeSet.operation).to.eq(ResourceOperation.MODIFY);
|
|
@@ -90,7 +90,7 @@ describe('Stateful parameter tests', () => {
|
|
|
90
90
|
{ propA: ['9.12', '9.13'] }, // b to remove, d, e, f to add
|
|
91
91
|
{ propA: ['9.12.9'] },
|
|
92
92
|
{ type: 'typeA' },
|
|
93
|
-
{ statefulMode: false,
|
|
93
|
+
{ statefulMode: false, parameterOptions: { propA: { isStatefulParameter: true }} }
|
|
94
94
|
);
|
|
95
95
|
|
|
96
96
|
expect(plan.changeSet.operation).to.eq(ResourceOperation.MODIFY);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Plan } from './plan.js';
|
|
2
2
|
import { StringIndexedObject } from 'codify-schemas';
|
|
3
3
|
|
|
4
|
-
export interface
|
|
4
|
+
export interface StatefulParameterOptions<T> {
|
|
5
5
|
name: keyof T;
|
|
6
6
|
isEqual?: (desired: any, current: any) => boolean;
|
|
7
7
|
|
|
@@ -17,7 +17,7 @@ export interface StatefulParameterConfiguration<T> {
|
|
|
17
17
|
disableStatelessModeArrayFiltering?: boolean;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export interface
|
|
20
|
+
export interface ArrayStatefulParameterOptions<T> extends StatefulParameterOptions<T> {
|
|
21
21
|
isEqual?: (desired: any[], current: any[]) => boolean;
|
|
22
22
|
isElementEqual?: (desired: any, current: any) => boolean;
|
|
23
23
|
}
|
|
@@ -25,11 +25,11 @@ export interface ArrayStatefulParameterConfiguration<T> extends StatefulParamete
|
|
|
25
25
|
|
|
26
26
|
export abstract class StatefulParameter<T extends StringIndexedObject, V extends T[keyof T]> {
|
|
27
27
|
readonly name: keyof T;
|
|
28
|
-
readonly
|
|
28
|
+
readonly options: StatefulParameterOptions<T>;
|
|
29
29
|
|
|
30
|
-
protected constructor(
|
|
31
|
-
this.name =
|
|
32
|
-
this.
|
|
30
|
+
protected constructor(options: StatefulParameterOptions<T>) {
|
|
31
|
+
this.name = options.name;
|
|
32
|
+
this.options = options
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
abstract refresh(desired: V | null): Promise<V | null>;
|
|
@@ -41,11 +41,11 @@ export abstract class StatefulParameter<T extends StringIndexedObject, V extends
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export abstract class ArrayStatefulParameter<T extends StringIndexedObject, V> extends StatefulParameter<T, any>{
|
|
44
|
-
|
|
44
|
+
options: ArrayStatefulParameterOptions<T>;
|
|
45
45
|
|
|
46
|
-
constructor(
|
|
47
|
-
super(
|
|
48
|
-
this.
|
|
46
|
+
constructor(options: ArrayStatefulParameterOptions<T>) {
|
|
47
|
+
super(options);
|
|
48
|
+
this.options = options;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
async applyAdd(valuesToAdd: V[], plan: Plan<T>): Promise<void> {
|
|
@@ -55,18 +55,18 @@ export abstract class ArrayStatefulParameter<T extends StringIndexedObject, V> e
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
async applyModify(newValues: V[], previousValues: V[], allowDeletes: boolean, plan: Plan<T>): Promise<void> {
|
|
58
|
-
const
|
|
58
|
+
const options = this.options as ArrayStatefulParameterOptions<T>;
|
|
59
59
|
|
|
60
60
|
const valuesToAdd = newValues.filter((n) => !previousValues.some((p) => {
|
|
61
|
-
if (
|
|
62
|
-
return
|
|
61
|
+
if (options.isElementEqual) {
|
|
62
|
+
return options.isElementEqual(n, p);
|
|
63
63
|
}
|
|
64
64
|
return n === p;
|
|
65
65
|
}));
|
|
66
66
|
|
|
67
67
|
const valuesToRemove = previousValues.filter((p) => !newValues.some((n) => {
|
|
68
|
-
if (
|
|
69
|
-
return
|
|
68
|
+
if (options.isElementEqual) {
|
|
69
|
+
return options.isElementEqual(n, p);
|
|
70
70
|
}
|
|
71
71
|
return n === p;
|
|
72
72
|
}));
|