codify-plugin-lib 1.0.50 → 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 -12
- package/dist/entities/resource.d.ts +17 -9
- package/dist/entities/resource.js +135 -85
- package/dist/entities/stateful-parameter.d.ts +6 -6
- package/dist/entities/stateful-parameter.js +13 -13
- 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 +224 -12
- package/src/entities/resource-types.ts +2 -16
- package/src/entities/resource.test.ts +16 -20
- package/src/entities/resource.ts +186 -119
- package/src/entities/stateful-parameter.test.ts +5 -5
- package/src/entities/stateful-parameter.ts +15 -15
package/src/entities/resource.ts
CHANGED
|
@@ -2,10 +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
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';
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* Description of resource here
|
|
@@ -15,95 +18,99 @@ import { TransformParameter } from './transform-parameter.js';
|
|
|
15
18
|
*
|
|
16
19
|
*/
|
|
17
20
|
export abstract class Resource<T extends StringIndexedObject> {
|
|
18
|
-
|
|
19
21
|
readonly typeId: string;
|
|
20
22
|
readonly statefulParameters: Map<keyof T, StatefulParameter<T, T[keyof T]>>;
|
|
21
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
|
+
|
|
22
29
|
readonly dependencies: string[]; // TODO: Change this to a string
|
|
23
|
-
readonly
|
|
24
|
-
readonly
|
|
30
|
+
readonly parameterOptions: Record<keyof T, ParameterOptions>
|
|
31
|
+
readonly options: ResourceOptions<T>;
|
|
25
32
|
readonly defaultValues: Partial<Record<keyof T, unknown>>;
|
|
26
33
|
|
|
27
|
-
protected
|
|
28
|
-
|
|
34
|
+
protected ajv?: Ajv.default;
|
|
35
|
+
protected schemaValidator?: ValidateFunction;
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
34
|
-
this.defaultValues = this.initializeDefaultValues(configuration);
|
|
37
|
+
protected constructor(options: ResourceOptions<T>) {
|
|
38
|
+
this.typeId = options.type;
|
|
39
|
+
this.dependencies = options.dependencies ?? [];
|
|
40
|
+
this.options = options;
|
|
35
41
|
|
|
36
|
-
this.
|
|
37
|
-
|
|
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
|
+
}
|
|
49
|
+
|
|
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;
|
|
38
58
|
}
|
|
39
59
|
|
|
40
60
|
async onInitialize(): Promise<void> {}
|
|
41
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
|
+
|
|
42
77
|
// TODO: Add state in later.
|
|
43
78
|
// Currently only calculating how to add things to reach desired state. Can't delete resources.
|
|
44
79
|
// Add previousConfig as a parameter for plan(desired, previous);
|
|
45
80
|
async plan(desiredConfig: Partial<T> & ResourceConfig): Promise<Plan<T>> {
|
|
46
|
-
|
|
47
|
-
// Explanation: these are settings for how the plan will be generated
|
|
48
|
-
const planConfiguration: PlanConfiguration<T> = {
|
|
81
|
+
const planOptions: PlanOptions<T> = {
|
|
49
82
|
statefulMode: false,
|
|
50
|
-
|
|
83
|
+
parameterOptions: this.parameterOptions,
|
|
51
84
|
}
|
|
52
85
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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;
|
|
61
94
|
|
|
62
|
-
|
|
63
|
-
|
|
95
|
+
this.addDefaultValues(resourceParameters);
|
|
96
|
+
await this.applyTransformParameters(resourceParameters);
|
|
64
97
|
|
|
65
|
-
// Refresh resource parameters
|
|
66
|
-
|
|
67
|
-
const entriesToRefresh = new Map(Object.entries(resourceParameters));
|
|
68
|
-
const currentParameters = await this.refresh(entriesToRefresh);
|
|
98
|
+
// Refresh resource parameters. This refreshes the parameters that configure the resource itself
|
|
99
|
+
const currentParameters = await this.refreshResourceParameters(resourceParameters);
|
|
69
100
|
|
|
70
|
-
// Short circuit here. If resource is non-existent,
|
|
101
|
+
// Short circuit here. If the resource is non-existent, there's no point checking stateful parameters
|
|
71
102
|
if (currentParameters == null) {
|
|
72
|
-
return Plan.create(desiredParameters, null, resourceMetadata,
|
|
103
|
+
return Plan.create(desiredParameters, null, resourceMetadata, planOptions);
|
|
73
104
|
}
|
|
74
105
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// Refresh stateful parameters
|
|
78
|
-
// This refreshes parameters that are stateful (they can be added, deleted separately from the resource)
|
|
79
|
-
for(const statefulParameter of statefulParameters) {
|
|
80
|
-
const desiredValue = desiredParameters[statefulParameter.name];
|
|
81
|
-
|
|
82
|
-
let currentValue = await statefulParameter.refresh(desiredValue ?? null) ?? undefined;
|
|
83
|
-
|
|
84
|
-
// In stateless mode, filter the refreshed parameters by the desired to ensure that no deletes happen
|
|
85
|
-
if (Array.isArray(currentValue)
|
|
86
|
-
&& Array.isArray(desiredValue)
|
|
87
|
-
&& !planConfiguration.statefulMode
|
|
88
|
-
&& !statefulParameter.configuration.disableStatelessModeArrayFiltering
|
|
89
|
-
) {
|
|
90
|
-
currentValue = currentValue.filter((c) => desiredValue?.some((d) => {
|
|
91
|
-
const pc = planConfiguration?.parameterConfigurations?.[statefulParameter.name];
|
|
92
|
-
if (pc && pc.isElementEqual) {
|
|
93
|
-
return pc.isElementEqual(d, c);
|
|
94
|
-
}
|
|
95
|
-
return d === c;
|
|
96
|
-
})) as any;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
currentParameters[statefulParameter.name] = currentValue;
|
|
100
|
-
}
|
|
106
|
+
// Refresh stateful parameters. These parameters have state external to the resource
|
|
107
|
+
const statefulCurrentParameters = await this.refreshStatefulParameters(statefulParameters, planOptions.statefulMode);
|
|
101
108
|
|
|
102
109
|
return Plan.create(
|
|
103
|
-
|
|
104
|
-
currentParameters as Partial<T>,
|
|
110
|
+
{ ...resourceParameters, ...statefulParameters },
|
|
111
|
+
{ ...currentParameters, ...statefulCurrentParameters } as Partial<T>,
|
|
105
112
|
resourceMetadata,
|
|
106
|
-
|
|
113
|
+
planOptions,
|
|
107
114
|
)
|
|
108
115
|
}
|
|
109
116
|
|
|
@@ -134,6 +141,8 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
134
141
|
|
|
135
142
|
const statefulParameterChanges = plan.changeSet.parameterChanges
|
|
136
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
|
+
|
|
137
146
|
for (const parameterChange of statefulParameterChanges) {
|
|
138
147
|
const statefulParameter = this.statefulParameters.get(parameterChange.name)!;
|
|
139
148
|
await statefulParameter.applyAdd(parameterChange.newValue, plan);
|
|
@@ -148,6 +157,7 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
148
157
|
|
|
149
158
|
const statelessParameterChanges = parameterChanges
|
|
150
159
|
.filter((pc: ParameterChange<T>) => !this.statefulParameters.has(pc.name))
|
|
160
|
+
|
|
151
161
|
for (const pc of statelessParameterChanges) {
|
|
152
162
|
// TODO: When stateful mode is added in the future. Dynamically choose if deletes are allowed
|
|
153
163
|
await this.applyModify(pc.name, pc.newValue, pc.previousValue, false, plan);
|
|
@@ -155,6 +165,8 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
155
165
|
|
|
156
166
|
const statefulParameterChanges = parameterChanges
|
|
157
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
|
+
|
|
158
170
|
for (const parameterChange of statefulParameterChanges) {
|
|
159
171
|
const statefulParameter = this.statefulParameters.get(parameterChange.name)!;
|
|
160
172
|
|
|
@@ -179,9 +191,11 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
179
191
|
private async _applyDestroy(plan: Plan<T>): Promise<void> {
|
|
180
192
|
// If this option is set (defaults to false), then stateful parameters need to be destroyed
|
|
181
193
|
// as well. This means that the stateful parameter wouldn't have been normally destroyed with applyDestroy()
|
|
182
|
-
if (this.
|
|
194
|
+
if (this.options.callStatefulParameterRemoveOnDestroy) {
|
|
183
195
|
const statefulParameterChanges = plan.changeSet.parameterChanges
|
|
184
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
|
+
|
|
185
199
|
for (const parameterChange of statefulParameterChanges) {
|
|
186
200
|
const statefulParameter = this.statefulParameters.get(parameterChange.name)!;
|
|
187
201
|
await statefulParameter.applyRemove(parameterChange.previousValue, plan);
|
|
@@ -191,58 +205,6 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
191
205
|
await this.applyDestroy(plan);
|
|
192
206
|
}
|
|
193
207
|
|
|
194
|
-
private initializeParameterConfigurations(
|
|
195
|
-
resourceConfiguration: ResourceConfiguration<T>
|
|
196
|
-
): Record<keyof T, ParameterConfiguration> {
|
|
197
|
-
const resourceParameters = Object.fromEntries(
|
|
198
|
-
Object.entries(resourceConfiguration.parameterConfigurations ?? {})
|
|
199
|
-
?.map(([name, value]) => ([name, { ...value, isStatefulParameter: false }]))
|
|
200
|
-
) as Record<keyof T, ParameterConfiguration>
|
|
201
|
-
|
|
202
|
-
const statefulParameters = resourceConfiguration.statefulParameters
|
|
203
|
-
?.reduce((obj, sp) => {
|
|
204
|
-
return {
|
|
205
|
-
...obj,
|
|
206
|
-
[sp.name]: {
|
|
207
|
-
...sp.configuration,
|
|
208
|
-
isStatefulParameter: true,
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}, {}) ?? {}
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
...resourceParameters,
|
|
215
|
-
...statefulParameters,
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
private initializeDefaultValues(
|
|
220
|
-
resourceConfiguration: ResourceConfiguration<T>
|
|
221
|
-
): Partial<Record<keyof T, unknown>> {
|
|
222
|
-
if (!resourceConfiguration.parameterConfigurations) {
|
|
223
|
-
return {};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return Object.fromEntries(
|
|
227
|
-
Object.entries(resourceConfiguration.parameterConfigurations)
|
|
228
|
-
.filter((p) => p[1]?.defaultValue !== undefined)
|
|
229
|
-
.map((config) => [config[0], config[1]!.defaultValue])
|
|
230
|
-
) as Partial<Record<keyof T, unknown>>;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
private validateResourceConfiguration(data: ResourceConfiguration<T>) {
|
|
234
|
-
// Stateful parameters are configured within the object not in the resource.
|
|
235
|
-
if (data.parameterConfigurations && data.statefulParameters) {
|
|
236
|
-
const parameters = [...Object.keys(data.parameterConfigurations)];
|
|
237
|
-
const statefulParameterSet = new Set(data.statefulParameters.map((sp) => sp.name));
|
|
238
|
-
|
|
239
|
-
const intersection = parameters.some((p) => statefulParameterSet.has(p));
|
|
240
|
-
if (intersection) {
|
|
241
|
-
throw new Error(`Resource ${this.typeId} cannot declare a parameter as both stateful and non-stateful`);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
208
|
private validateRefreshResults(refresh: Partial<T> | null, desiredMap: Map<keyof T, T[keyof T]>) {
|
|
247
209
|
if (!refresh) {
|
|
248
210
|
return;
|
|
@@ -253,7 +215,7 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
253
215
|
|
|
254
216
|
if (!setsEqual(desiredKeys, refreshKeys)) {
|
|
255
217
|
throw new Error(
|
|
256
|
-
`Resource ${this.
|
|
218
|
+
`Resource ${this.typeId}
|
|
257
219
|
refresh() must return back exactly the keys that were provided
|
|
258
220
|
Missing: ${[...desiredKeys].filter((k) => !refreshKeys.has(k))};
|
|
259
221
|
Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
@@ -262,7 +224,10 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
|
262
224
|
}
|
|
263
225
|
|
|
264
226
|
private async applyTransformParameters(desired: Partial<T>): Promise<void> {
|
|
265
|
-
|
|
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) {
|
|
266
231
|
if (desired[key] !== null) {
|
|
267
232
|
const transformedValue = await tp.transform(desired[key]);
|
|
268
233
|
|
|
@@ -290,7 +255,59 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
|
290
255
|
});
|
|
291
256
|
}
|
|
292
257
|
|
|
293
|
-
|
|
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
|
+
};
|
|
294
311
|
|
|
295
312
|
abstract refresh(keys: Map<keyof T, T[keyof T]>): Promise<Partial<T> | null>;
|
|
296
313
|
|
|
@@ -300,3 +317,53 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
|
300
317
|
|
|
301
318
|
abstract applyDestroy(plan: Plan<T>): Promise<void>;
|
|
302
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
|
}));
|