codify-plugin-lib 1.0.50 → 1.0.52
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 +136 -86
- 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 +261 -12
- package/src/entities/resource-types.ts +2 -16
- package/src/entities/resource.test.ts +16 -20
- package/src/entities/resource.ts +188 -120
- 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,100 @@ 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
|
+
transformParameters,
|
|
94
|
+
} = parsedConfig;
|
|
61
95
|
|
|
62
|
-
|
|
63
|
-
|
|
96
|
+
this.addDefaultValues(resourceParameters);
|
|
97
|
+
await this.applyTransformParameters(transformParameters, resourceParameters);
|
|
64
98
|
|
|
65
|
-
// Refresh resource parameters
|
|
66
|
-
|
|
67
|
-
const entriesToRefresh = new Map(Object.entries(resourceParameters));
|
|
68
|
-
const currentParameters = await this.refresh(entriesToRefresh);
|
|
99
|
+
// Refresh resource parameters. This refreshes the parameters that configure the resource itself
|
|
100
|
+
const currentParameters = await this.refreshResourceParameters(resourceParameters);
|
|
69
101
|
|
|
70
|
-
// Short circuit here. If resource is non-existent,
|
|
102
|
+
// Short circuit here. If the resource is non-existent, there's no point checking stateful parameters
|
|
71
103
|
if (currentParameters == null) {
|
|
72
|
-
return Plan.create(desiredParameters, null, resourceMetadata,
|
|
104
|
+
return Plan.create(desiredParameters, null, resourceMetadata, planOptions);
|
|
73
105
|
}
|
|
74
106
|
|
|
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
|
-
}
|
|
107
|
+
// Refresh stateful parameters. These parameters have state external to the resource
|
|
108
|
+
const statefulCurrentParameters = await this.refreshStatefulParameters(statefulParameters, planOptions.statefulMode);
|
|
101
109
|
|
|
102
110
|
return Plan.create(
|
|
103
|
-
|
|
104
|
-
currentParameters as Partial<T>,
|
|
111
|
+
{ ...resourceParameters, ...statefulParameters },
|
|
112
|
+
{ ...currentParameters, ...statefulCurrentParameters } as Partial<T>,
|
|
105
113
|
resourceMetadata,
|
|
106
|
-
|
|
114
|
+
planOptions,
|
|
107
115
|
)
|
|
108
116
|
}
|
|
109
117
|
|
|
@@ -134,6 +142,8 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
134
142
|
|
|
135
143
|
const statefulParameterChanges = plan.changeSet.parameterChanges
|
|
136
144
|
.filter((pc: ParameterChange<T>) => this.statefulParameters.has(pc.name))
|
|
145
|
+
.sort((a, b) => this.statefulParameterOrder.get(a.name)! - this.statefulParameterOrder.get(b.name)!)
|
|
146
|
+
|
|
137
147
|
for (const parameterChange of statefulParameterChanges) {
|
|
138
148
|
const statefulParameter = this.statefulParameters.get(parameterChange.name)!;
|
|
139
149
|
await statefulParameter.applyAdd(parameterChange.newValue, plan);
|
|
@@ -148,6 +158,7 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
148
158
|
|
|
149
159
|
const statelessParameterChanges = parameterChanges
|
|
150
160
|
.filter((pc: ParameterChange<T>) => !this.statefulParameters.has(pc.name))
|
|
161
|
+
|
|
151
162
|
for (const pc of statelessParameterChanges) {
|
|
152
163
|
// TODO: When stateful mode is added in the future. Dynamically choose if deletes are allowed
|
|
153
164
|
await this.applyModify(pc.name, pc.newValue, pc.previousValue, false, plan);
|
|
@@ -155,6 +166,8 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
155
166
|
|
|
156
167
|
const statefulParameterChanges = parameterChanges
|
|
157
168
|
.filter((pc: ParameterChange<T>) => this.statefulParameters.has(pc.name))
|
|
169
|
+
.sort((a, b) => this.statefulParameterOrder.get(a.name)! - this.statefulParameterOrder.get(b.name)!)
|
|
170
|
+
|
|
158
171
|
for (const parameterChange of statefulParameterChanges) {
|
|
159
172
|
const statefulParameter = this.statefulParameters.get(parameterChange.name)!;
|
|
160
173
|
|
|
@@ -179,9 +192,11 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
179
192
|
private async _applyDestroy(plan: Plan<T>): Promise<void> {
|
|
180
193
|
// If this option is set (defaults to false), then stateful parameters need to be destroyed
|
|
181
194
|
// as well. This means that the stateful parameter wouldn't have been normally destroyed with applyDestroy()
|
|
182
|
-
if (this.
|
|
195
|
+
if (this.options.callStatefulParameterRemoveOnDestroy) {
|
|
183
196
|
const statefulParameterChanges = plan.changeSet.parameterChanges
|
|
184
197
|
.filter((pc: ParameterChange<T>) => this.statefulParameters.has(pc.name))
|
|
198
|
+
.sort((a, b) => this.statefulParameterOrder.get(a.name)! - this.statefulParameterOrder.get(b.name)!)
|
|
199
|
+
|
|
185
200
|
for (const parameterChange of statefulParameterChanges) {
|
|
186
201
|
const statefulParameter = this.statefulParameters.get(parameterChange.name)!;
|
|
187
202
|
await statefulParameter.applyRemove(parameterChange.previousValue, plan);
|
|
@@ -191,58 +206,6 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
191
206
|
await this.applyDestroy(plan);
|
|
192
207
|
}
|
|
193
208
|
|
|
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
209
|
private validateRefreshResults(refresh: Partial<T> | null, desiredMap: Map<keyof T, T[keyof T]>) {
|
|
247
210
|
if (!refresh) {
|
|
248
211
|
return;
|
|
@@ -253,7 +216,7 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
253
216
|
|
|
254
217
|
if (!setsEqual(desiredKeys, refreshKeys)) {
|
|
255
218
|
throw new Error(
|
|
256
|
-
`Resource ${this.
|
|
219
|
+
`Resource ${this.typeId}
|
|
257
220
|
refresh() must return back exactly the keys that were provided
|
|
258
221
|
Missing: ${[...desiredKeys].filter((k) => !refreshKeys.has(k))};
|
|
259
222
|
Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
@@ -261,8 +224,11 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
|
261
224
|
}
|
|
262
225
|
}
|
|
263
226
|
|
|
264
|
-
private async applyTransformParameters(desired: Partial<T>): Promise<void> {
|
|
265
|
-
|
|
227
|
+
private async applyTransformParameters(transformParameters: Partial<T>, desired: Partial<T>): Promise<void> {
|
|
228
|
+
const orderedEntries = [...Object.entries(transformParameters)]
|
|
229
|
+
.sort(([keyA], [keyB]) => this.transformParameterOrder.get(keyA)! - this.transformParameterOrder.get(keyB)!)
|
|
230
|
+
|
|
231
|
+
for (const [key, tp] of orderedEntries) {
|
|
266
232
|
if (desired[key] !== null) {
|
|
267
233
|
const transformedValue = await tp.transform(desired[key]);
|
|
268
234
|
|
|
@@ -290,7 +256,59 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
|
290
256
|
});
|
|
291
257
|
}
|
|
292
258
|
|
|
293
|
-
|
|
259
|
+
private async refreshResourceParameters(resourceParameters: Partial<T>): Promise<Partial<T> | null> {
|
|
260
|
+
const entriesToRefresh = new Map(Object.entries(resourceParameters));
|
|
261
|
+
const currentParameters = await this.refresh(entriesToRefresh);
|
|
262
|
+
|
|
263
|
+
this.validateRefreshResults(currentParameters, entriesToRefresh);
|
|
264
|
+
return currentParameters;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Refresh stateful parameters
|
|
268
|
+
// This refreshes parameters that are stateful (they can be added, deleted separately from the resource)
|
|
269
|
+
private async refreshStatefulParameters(statefulParametersConfig: Partial<T>, isStatefulMode: boolean): Promise<Partial<T>> {
|
|
270
|
+
const currentParameters: Partial<T> = {}
|
|
271
|
+
const sortedEntries = Object.entries(statefulParametersConfig)
|
|
272
|
+
.sort(([key1], [key2]) => this.statefulParameterOrder.get(key1)! - this.statefulParameterOrder.get(key2)!)
|
|
273
|
+
|
|
274
|
+
for(const [key, desiredValue] of sortedEntries) {
|
|
275
|
+
const statefulParameter = this.statefulParameters.get(key);
|
|
276
|
+
if (!statefulParameter) {
|
|
277
|
+
throw new Error(`Stateful parameter ${key} was not found`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let currentValue = await statefulParameter.refresh(desiredValue ?? null);
|
|
281
|
+
|
|
282
|
+
// In stateless mode, filter the refreshed parameters by the desired to ensure that no deletes happen
|
|
283
|
+
// Otherwise the change set will pick up the extra keys from the current and try to delete them
|
|
284
|
+
// This allows arrays within stateful parameters to be first class objects
|
|
285
|
+
if (Array.isArray(currentValue)
|
|
286
|
+
&& Array.isArray(desiredValue)
|
|
287
|
+
&& !isStatefulMode
|
|
288
|
+
&& !statefulParameter.options.disableStatelessModeArrayFiltering
|
|
289
|
+
) {
|
|
290
|
+
currentValue = currentValue.filter((c) => desiredValue?.some((d) => {
|
|
291
|
+
const parameterOptions = statefulParameter.options as any;
|
|
292
|
+
if (parameterOptions && parameterOptions.isElementEqual) {
|
|
293
|
+
return parameterOptions.isElementEqual(d, c);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return d === c;
|
|
297
|
+
})) as any;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// @ts-ignore
|
|
301
|
+
currentParameters[key] = currentValue;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return currentParameters;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async validate(parameters: unknown): Promise<ValidationResult> {
|
|
308
|
+
return {
|
|
309
|
+
isValid: true,
|
|
310
|
+
}
|
|
311
|
+
};
|
|
294
312
|
|
|
295
313
|
abstract refresh(keys: Map<keyof T, T[keyof T]>): Promise<Partial<T> | null>;
|
|
296
314
|
|
|
@@ -300,3 +318,53 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
|
300
318
|
|
|
301
319
|
abstract applyDestroy(plan: Plan<T>): Promise<void>;
|
|
302
320
|
}
|
|
321
|
+
|
|
322
|
+
class ConfigParser<T extends StringIndexedObject> {
|
|
323
|
+
private config: Partial<T> & ResourceConfig;
|
|
324
|
+
private statefulParametersMap: Map<keyof T, StatefulParameter<T, T[keyof T]>>;
|
|
325
|
+
private transformParametersMap: Map<keyof T, TransformParameter<T>>;
|
|
326
|
+
|
|
327
|
+
constructor(
|
|
328
|
+
config: Partial<T> & ResourceConfig,
|
|
329
|
+
statefulParameters: Map<keyof T, StatefulParameter<T, T[keyof T]>>,
|
|
330
|
+
transformParameters: Map<keyof T, TransformParameter<T>>,
|
|
331
|
+
) {
|
|
332
|
+
this.config = config;
|
|
333
|
+
this.statefulParametersMap = statefulParameters;
|
|
334
|
+
this.transformParametersMap = transformParameters;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
get resourceMetadata(): ResourceConfig {
|
|
338
|
+
const { resourceMetadata } = splitUserConfig(this.config);
|
|
339
|
+
return resourceMetadata;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
get parameters(): Partial<T> {
|
|
343
|
+
const { parameters } = splitUserConfig(this.config);
|
|
344
|
+
return parameters;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
get resourceParameters(): Partial<T> {
|
|
348
|
+
const parameters = this.parameters;
|
|
349
|
+
|
|
350
|
+
return Object.fromEntries([
|
|
351
|
+
...Object.entries(parameters).filter(([key]) => !(this.statefulParametersMap.has(key) || this.transformParametersMap.has(key))),
|
|
352
|
+
]) as Partial<T>;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
get statefulParameters(): Partial<T> {
|
|
356
|
+
const parameters = this.parameters;
|
|
357
|
+
|
|
358
|
+
return Object.fromEntries([
|
|
359
|
+
...Object.entries(parameters).filter(([key]) => this.statefulParametersMap.has(key)),
|
|
360
|
+
]) as Partial<T>;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
get transformParameters(): Partial<T> {
|
|
364
|
+
const parameters = this.parameters;
|
|
365
|
+
|
|
366
|
+
return Object.fromEntries([
|
|
367
|
+
...Object.entries(parameters).filter(([key]) => this.transformParametersMap.has(key)),
|
|
368
|
+
]) as Partial<T>;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -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
|
}));
|