@codifycli/plugin-core 1.0.0-beta1
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/.eslintignore +2 -0
- package/.eslintrc.json +30 -0
- package/.github/workflows/release.yaml +19 -0
- package/.github/workflows/unit-test-ci.yaml +18 -0
- package/.prettierrc.json +1 -0
- package/bin/build.js +189 -0
- package/dist/bin/build.d.ts +1 -0
- package/dist/bin/build.js +80 -0
- package/dist/bin/deploy-plugin.d.ts +2 -0
- package/dist/bin/deploy-plugin.js +8 -0
- package/dist/common/errors.d.ts +8 -0
- package/dist/common/errors.js +24 -0
- package/dist/entities/change-set.d.ts +24 -0
- package/dist/entities/change-set.js +152 -0
- package/dist/entities/errors.d.ts +4 -0
- package/dist/entities/errors.js +7 -0
- package/dist/entities/plan-types.d.ts +25 -0
- package/dist/entities/plan-types.js +1 -0
- package/dist/entities/plan.d.ts +15 -0
- package/dist/entities/plan.js +127 -0
- package/dist/entities/plugin.d.ts +16 -0
- package/dist/entities/plugin.js +80 -0
- package/dist/entities/resource-options.d.ts +31 -0
- package/dist/entities/resource-options.js +76 -0
- package/dist/entities/resource-types.d.ts +11 -0
- package/dist/entities/resource-types.js +1 -0
- package/dist/entities/resource.d.ts +42 -0
- package/dist/entities/resource.js +303 -0
- package/dist/entities/stateful-parameter.d.ts +29 -0
- package/dist/entities/stateful-parameter.js +46 -0
- package/dist/entities/transform-parameter.d.ts +4 -0
- package/dist/entities/transform-parameter.js +2 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +7 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +26 -0
- package/dist/messages/handlers.d.ts +14 -0
- package/dist/messages/handlers.js +134 -0
- package/dist/messages/sender.d.ts +11 -0
- package/dist/messages/sender.js +57 -0
- package/dist/plan/change-set.d.ts +53 -0
- package/dist/plan/change-set.js +153 -0
- package/dist/plan/plan-types.d.ts +23 -0
- package/dist/plan/plan-types.js +1 -0
- package/dist/plan/plan.d.ts +66 -0
- package/dist/plan/plan.js +328 -0
- package/dist/plugin/plugin.d.ts +24 -0
- package/dist/plugin/plugin.js +200 -0
- package/dist/pty/background-pty.d.ts +21 -0
- package/dist/pty/background-pty.js +127 -0
- package/dist/pty/index.d.ts +50 -0
- package/dist/pty/index.js +20 -0
- package/dist/pty/promise-queue.d.ts +5 -0
- package/dist/pty/promise-queue.js +26 -0
- package/dist/pty/seqeuntial-pty.d.ts +17 -0
- package/dist/pty/seqeuntial-pty.js +119 -0
- package/dist/pty/vitest.config.d.ts +2 -0
- package/dist/pty/vitest.config.js +11 -0
- package/dist/resource/config-parser.d.ts +11 -0
- package/dist/resource/config-parser.js +21 -0
- package/dist/resource/parsed-resource-settings.d.ts +47 -0
- package/dist/resource/parsed-resource-settings.js +196 -0
- package/dist/resource/resource-controller.d.ts +36 -0
- package/dist/resource/resource-controller.js +402 -0
- package/dist/resource/resource-settings.d.ts +303 -0
- package/dist/resource/resource-settings.js +147 -0
- package/dist/resource/resource.d.ts +144 -0
- package/dist/resource/resource.js +44 -0
- package/dist/resource/stateful-parameter.d.ts +165 -0
- package/dist/resource/stateful-parameter.js +94 -0
- package/dist/scripts/deploy.d.ts +1 -0
- package/dist/scripts/deploy.js +2 -0
- package/dist/stateful-parameter/stateful-parameter-controller.d.ts +21 -0
- package/dist/stateful-parameter/stateful-parameter-controller.js +81 -0
- package/dist/stateful-parameter/stateful-parameter.d.ts +144 -0
- package/dist/stateful-parameter/stateful-parameter.js +43 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +5 -0
- package/dist/utils/codify-spawn.d.ts +29 -0
- package/dist/utils/codify-spawn.js +136 -0
- package/dist/utils/debug.d.ts +2 -0
- package/dist/utils/debug.js +10 -0
- package/dist/utils/file-utils.d.ts +23 -0
- package/dist/utils/file-utils.js +186 -0
- package/dist/utils/functions.d.ts +12 -0
- package/dist/utils/functions.js +74 -0
- package/dist/utils/index.d.ts +46 -0
- package/dist/utils/index.js +271 -0
- package/dist/utils/internal-utils.d.ts +12 -0
- package/dist/utils/internal-utils.js +74 -0
- package/dist/utils/load-resources.d.ts +1 -0
- package/dist/utils/load-resources.js +46 -0
- package/dist/utils/package-json-utils.d.ts +12 -0
- package/dist/utils/package-json-utils.js +34 -0
- package/dist/utils/pty-local-storage.d.ts +2 -0
- package/dist/utils/pty-local-storage.js +2 -0
- package/dist/utils/spawn-2.d.ts +5 -0
- package/dist/utils/spawn-2.js +7 -0
- package/dist/utils/spawn.d.ts +29 -0
- package/dist/utils/spawn.js +124 -0
- package/dist/utils/utils.d.ts +18 -0
- package/dist/utils/utils.js +86 -0
- package/dist/utils/verbosity-level.d.ts +5 -0
- package/dist/utils/verbosity-level.js +9 -0
- package/package.json +59 -0
- package/rollup.config.js +24 -0
- package/src/common/errors.test.ts +43 -0
- package/src/common/errors.ts +31 -0
- package/src/errors.ts +8 -0
- package/src/index.test.ts +6 -0
- package/src/index.ts +30 -0
- package/src/messages/handlers.test.ts +329 -0
- package/src/messages/handlers.ts +181 -0
- package/src/messages/sender.ts +69 -0
- package/src/plan/change-set.test.ts +280 -0
- package/src/plan/change-set.ts +236 -0
- package/src/plan/plan-types.ts +27 -0
- package/src/plan/plan.test.ts +413 -0
- package/src/plan/plan.ts +499 -0
- package/src/plugin/plugin.test.ts +533 -0
- package/src/plugin/plugin.ts +291 -0
- package/src/pty/background-pty.test.ts +69 -0
- package/src/pty/background-pty.ts +154 -0
- package/src/pty/index.test.ts +129 -0
- package/src/pty/index.ts +66 -0
- package/src/pty/promise-queue.ts +33 -0
- package/src/pty/seqeuntial-pty.ts +151 -0
- package/src/pty/sequential-pty.test.ts +194 -0
- package/src/resource/config-parser.ts +42 -0
- package/src/resource/parsed-resource-settings.test.ts +186 -0
- package/src/resource/parsed-resource-settings.ts +307 -0
- package/src/resource/resource-controller-stateful-mode.test.ts +253 -0
- package/src/resource/resource-controller.test.ts +1081 -0
- package/src/resource/resource-controller.ts +563 -0
- package/src/resource/resource-settings.test.ts +1213 -0
- package/src/resource/resource-settings.ts +545 -0
- package/src/resource/resource.ts +157 -0
- package/src/stateful-parameter/stateful-parameter-controller.test.ts +244 -0
- package/src/stateful-parameter/stateful-parameter-controller.ts +111 -0
- package/src/stateful-parameter/stateful-parameter.ts +160 -0
- package/src/utils/debug.ts +11 -0
- package/src/utils/file-utils.test.ts +7 -0
- package/src/utils/file-utils.ts +231 -0
- package/src/utils/functions.ts +103 -0
- package/src/utils/index.ts +340 -0
- package/src/utils/internal-utils.test.ts +52 -0
- package/src/utils/pty-local-storage.ts +3 -0
- package/src/utils/test-utils.test.ts +96 -0
- package/src/utils/verbosity-level.ts +11 -0
- package/tsconfig.json +26 -0
- package/tsconfig.test.json +9 -0
- package/vitest.config.ts +10 -0
package/src/plan/plan.ts
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApplyRequestData,
|
|
3
|
+
ParameterOperation,
|
|
4
|
+
PlanResponseData,
|
|
5
|
+
ResourceConfig,
|
|
6
|
+
ResourceOperation,
|
|
7
|
+
StringIndexedObject,
|
|
8
|
+
} from 'codify-schemas';
|
|
9
|
+
import { v4 as uuidV4 } from 'uuid';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
ParsedArrayParameterSetting,
|
|
13
|
+
ParsedResourceSettings,
|
|
14
|
+
ParsedStatefulParameterSetting
|
|
15
|
+
} from '../resource/parsed-resource-settings.js';
|
|
16
|
+
import { ArrayParameterSetting, ResourceSettings } from '../resource/resource-settings.js';
|
|
17
|
+
import { ChangeSet } from './change-set.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A plan represents a set of actions that after taken will turn the current resource into the desired one.
|
|
21
|
+
* A plan consists of list of parameter level changes (ADD, REMOVE, MODIFY or NO-OP) as well as a resource level
|
|
22
|
+
* operation (CREATE, DESTROY, MODIFY, RE-CREATE, NO-OP).
|
|
23
|
+
*/
|
|
24
|
+
export class Plan<T extends StringIndexedObject> {
|
|
25
|
+
id: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* List of changes to make
|
|
29
|
+
*/
|
|
30
|
+
changeSet: ChangeSet<T>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Ex: name, type, dependsOn etc. Metadata parameters
|
|
34
|
+
*/
|
|
35
|
+
coreParameters: ResourceConfig;
|
|
36
|
+
|
|
37
|
+
isStateful: boolean;
|
|
38
|
+
|
|
39
|
+
constructor(id: string, changeSet: ChangeSet<T>, coreParameters: ResourceConfig, isStateful: boolean) {
|
|
40
|
+
this.id = id;
|
|
41
|
+
this.changeSet = changeSet;
|
|
42
|
+
this.coreParameters = coreParameters;
|
|
43
|
+
this.isStateful = isStateful;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The desired config that a plan will achieve after executing all the actions.
|
|
48
|
+
*/
|
|
49
|
+
get desiredConfig(): T | null {
|
|
50
|
+
if (this.changeSet.operation === ResourceOperation.DESTROY) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return this.changeSet.desiredParameters;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The current config that the plan is changing.
|
|
59
|
+
*/
|
|
60
|
+
get currentConfig(): T | null {
|
|
61
|
+
if (this.changeSet.operation === ResourceOperation.CREATE) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return this.changeSet.currentParameters;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get resourceId(): string {
|
|
69
|
+
return this.coreParameters.name
|
|
70
|
+
? `${this.coreParameters.type}.${this.coreParameters.name}`
|
|
71
|
+
: this.coreParameters.type;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static calculate<T extends StringIndexedObject>(params: {
|
|
75
|
+
desired: Partial<T> | null,
|
|
76
|
+
currentArray: Partial<T>[] | null,
|
|
77
|
+
state: Partial<T> | null,
|
|
78
|
+
core: ResourceConfig,
|
|
79
|
+
settings: ParsedResourceSettings<T>,
|
|
80
|
+
isStateful: boolean,
|
|
81
|
+
}): Plan<T> {
|
|
82
|
+
const {
|
|
83
|
+
desired,
|
|
84
|
+
currentArray,
|
|
85
|
+
state,
|
|
86
|
+
core,
|
|
87
|
+
settings,
|
|
88
|
+
isStateful
|
|
89
|
+
} = params
|
|
90
|
+
|
|
91
|
+
const current = Plan.matchCurrentParameters<T>({
|
|
92
|
+
desired,
|
|
93
|
+
currentArray,
|
|
94
|
+
state,
|
|
95
|
+
settings,
|
|
96
|
+
isStateful
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const filteredCurrentParameters = Plan.filterCurrentParams<T>({
|
|
100
|
+
desired,
|
|
101
|
+
current,
|
|
102
|
+
state,
|
|
103
|
+
settings,
|
|
104
|
+
isStateful
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Empty
|
|
108
|
+
if (!filteredCurrentParameters && !desired) {
|
|
109
|
+
return new Plan(
|
|
110
|
+
uuidV4(),
|
|
111
|
+
ChangeSet.empty<T>(),
|
|
112
|
+
core,
|
|
113
|
+
isStateful,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// CREATE
|
|
118
|
+
if (!filteredCurrentParameters && desired) {
|
|
119
|
+
return new Plan(
|
|
120
|
+
uuidV4(),
|
|
121
|
+
ChangeSet.create(desired, settings),
|
|
122
|
+
core,
|
|
123
|
+
isStateful,
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// DESTROY
|
|
128
|
+
if (filteredCurrentParameters && !desired) {
|
|
129
|
+
// We can manually override destroys. If a resource cannot be destroyed (for instance the npm resource relies on NodeJS being created and destroyed)
|
|
130
|
+
if (!settings.canDestroy) {
|
|
131
|
+
return new Plan(
|
|
132
|
+
uuidV4(),
|
|
133
|
+
ChangeSet.noop(filteredCurrentParameters, settings),
|
|
134
|
+
core,
|
|
135
|
+
isStateful,
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return new Plan(
|
|
140
|
+
uuidV4(),
|
|
141
|
+
ChangeSet.destroy(filteredCurrentParameters, settings),
|
|
142
|
+
core,
|
|
143
|
+
isStateful,
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// NO-OP, MODIFY or RE-CREATE
|
|
148
|
+
const changeSet = ChangeSet.calculateModification(
|
|
149
|
+
desired!,
|
|
150
|
+
filteredCurrentParameters!,
|
|
151
|
+
settings.parameterSettings,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return new Plan(
|
|
155
|
+
uuidV4(),
|
|
156
|
+
changeSet,
|
|
157
|
+
core,
|
|
158
|
+
isStateful,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 2. Even if there was (maybe for testing reasons), the plan values should not be adjusted
|
|
163
|
+
static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues?: Partial<Record<keyof T, unknown>>): Plan<T> {
|
|
164
|
+
if (!data) {
|
|
165
|
+
throw new Error('Data is empty');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
addDefaultValues();
|
|
169
|
+
|
|
170
|
+
return new Plan(
|
|
171
|
+
uuidV4(),
|
|
172
|
+
new ChangeSet<T>(
|
|
173
|
+
data.operation,
|
|
174
|
+
data.parameters.map((p) => ({
|
|
175
|
+
...p,
|
|
176
|
+
isSensitive: p.isSensitive ?? false,
|
|
177
|
+
})),
|
|
178
|
+
),
|
|
179
|
+
{
|
|
180
|
+
type: data.resourceType,
|
|
181
|
+
name: data.resourceName,
|
|
182
|
+
},
|
|
183
|
+
data.isStateful
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
function addDefaultValues(): void {
|
|
187
|
+
Object.entries(defaultValues ?? {})
|
|
188
|
+
.forEach(([key, defaultValue]) => {
|
|
189
|
+
const configValueExists = data!
|
|
190
|
+
.parameters
|
|
191
|
+
.some((p) => p.name === key);
|
|
192
|
+
|
|
193
|
+
// Only set default values if the value does not exist in the config
|
|
194
|
+
if (configValueExists) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
switch (data!.operation) {
|
|
199
|
+
case ResourceOperation.CREATE: {
|
|
200
|
+
data!.parameters.push({
|
|
201
|
+
name: key,
|
|
202
|
+
operation: ParameterOperation.ADD,
|
|
203
|
+
previousValue: null,
|
|
204
|
+
newValue: defaultValue,
|
|
205
|
+
});
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
case ResourceOperation.DESTROY: {
|
|
210
|
+
data!.parameters.push({
|
|
211
|
+
name: key,
|
|
212
|
+
operation: ParameterOperation.REMOVE,
|
|
213
|
+
previousValue: defaultValue,
|
|
214
|
+
newValue: null,
|
|
215
|
+
});
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
case ResourceOperation.MODIFY:
|
|
220
|
+
case ResourceOperation.RECREATE:
|
|
221
|
+
case ResourceOperation.NOOP: {
|
|
222
|
+
data!.parameters.push({
|
|
223
|
+
name: key,
|
|
224
|
+
operation: ParameterOperation.NOOP,
|
|
225
|
+
previousValue: defaultValue,
|
|
226
|
+
newValue: defaultValue,
|
|
227
|
+
});
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The type (id) of the resource
|
|
238
|
+
*
|
|
239
|
+
* @return string
|
|
240
|
+
*/
|
|
241
|
+
getResourceType(): string {
|
|
242
|
+
return this.coreParameters.type
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* When multiples of the same resource are allowed, this matching function will match a given config with one of the
|
|
247
|
+
* existing configs on the system. For example if there are multiple versions of Android Studios installed, we can use
|
|
248
|
+
* the application name and location to match it to our desired configs name and location.
|
|
249
|
+
*
|
|
250
|
+
* @param params
|
|
251
|
+
* @private
|
|
252
|
+
*/
|
|
253
|
+
private static matchCurrentParameters<T extends StringIndexedObject>(params: {
|
|
254
|
+
desired: Partial<T> | null,
|
|
255
|
+
currentArray: Partial<T>[] | null,
|
|
256
|
+
state: Partial<T> | null,
|
|
257
|
+
settings: ParsedResourceSettings<T>,
|
|
258
|
+
isStateful: boolean,
|
|
259
|
+
}): Partial<T> | null {
|
|
260
|
+
const {
|
|
261
|
+
desired,
|
|
262
|
+
currentArray,
|
|
263
|
+
state,
|
|
264
|
+
settings,
|
|
265
|
+
isStateful
|
|
266
|
+
} = params;
|
|
267
|
+
|
|
268
|
+
if (!settings.allowMultiple) {
|
|
269
|
+
return currentArray?.[0] ?? null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!currentArray) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const { matcher: parameterMatcher, id } = settings;
|
|
277
|
+
const matcher = (desired: Partial<T>, currentArray: Partial<T>[]): Partial<T> | undefined => {
|
|
278
|
+
const matched = currentArray.filter((c) => parameterMatcher(desired, c))
|
|
279
|
+
if (matched.length > 1) {
|
|
280
|
+
console.log(`Resource: ${id} did not uniquely match resources when allow multiple is set to true`)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return matched[0];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (isStateful) {
|
|
287
|
+
return state
|
|
288
|
+
? matcher(state, currentArray) ?? null
|
|
289
|
+
: null
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return matcher(desired!, currentArray) ?? null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Only keep relevant params for the plan. We don't want to change settings that were not already
|
|
297
|
+
* defined.
|
|
298
|
+
*
|
|
299
|
+
* 1. In stateless mode, filter current by desired. We only want to know about settings that the user has specified
|
|
300
|
+
* 2. In stateful mode, filter current by state and desired. We only know about the settings the user has previously set
|
|
301
|
+
* or wants to set. If a parameter is not specified then it's not managed by Codify.
|
|
302
|
+
*/
|
|
303
|
+
private static filterCurrentParams<T extends StringIndexedObject>(params: {
|
|
304
|
+
desired: Partial<T> | null,
|
|
305
|
+
current: Partial<T> | null,
|
|
306
|
+
state: Partial<T> | null,
|
|
307
|
+
settings: ResourceSettings<T>,
|
|
308
|
+
isStateful: boolean,
|
|
309
|
+
}): Partial<T> | null {
|
|
310
|
+
const {
|
|
311
|
+
desired,
|
|
312
|
+
current,
|
|
313
|
+
state,
|
|
314
|
+
settings,
|
|
315
|
+
isStateful
|
|
316
|
+
} = params;
|
|
317
|
+
|
|
318
|
+
if (!current) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const filteredCurrent = filterCurrent()
|
|
323
|
+
if (!filteredCurrent) {
|
|
324
|
+
return null
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// For stateful mode, we're done after filtering by the keys of desired + state. Stateless mode
|
|
328
|
+
// requires additional filtering for stateful parameter arrays and objects.
|
|
329
|
+
if (isStateful && desired) {
|
|
330
|
+
return filteredCurrent;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// We also want to filter parameters when in delete mode. We don't want to delete parameters that
|
|
334
|
+
// are not specified in the original config.
|
|
335
|
+
if (isStateful && !desired) {
|
|
336
|
+
const arrayStatefulParameters = Object.fromEntries(
|
|
337
|
+
Object.entries(filteredCurrent)
|
|
338
|
+
.filter(([k, v]) => isArrayParameterWithFiltering(k, v))
|
|
339
|
+
.map(([k, v]) => [k, filterArrayParameterForDeletes(k, v)])
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
return { ...filteredCurrent, ...arrayStatefulParameters }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// TODO: Add object handling here in addition to arrays in the future
|
|
346
|
+
const arrayStatefulParameters = Object.fromEntries(
|
|
347
|
+
Object.entries(filteredCurrent)
|
|
348
|
+
.filter(([k, v]) => isArrayParameterWithFiltering(k, v))
|
|
349
|
+
.map(([k, v]) => [k, filterArrayParameterForStatelessMode(k, v)])
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
return { ...filteredCurrent, ...arrayStatefulParameters }
|
|
353
|
+
|
|
354
|
+
function filterCurrent(): Partial<T> | null {
|
|
355
|
+
if (!current) {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (isStateful) {
|
|
360
|
+
const keys = new Set([...Object.keys(state ?? {}), ...Object.keys(desired ?? {})]);
|
|
361
|
+
return Object.fromEntries(
|
|
362
|
+
Object.entries(current)
|
|
363
|
+
.filter(([k]) => keys.has(k))
|
|
364
|
+
) as Partial<T>;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Stateless mode
|
|
368
|
+
const keys = new Set(Object.keys(desired ?? {}));
|
|
369
|
+
return Object.fromEntries(
|
|
370
|
+
Object.entries(current)
|
|
371
|
+
.filter(([k]) => keys.has(k))
|
|
372
|
+
) as Partial<T>;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function getFilterParameter(k: string): ((desired: any[], current: any[]) => any[]) | boolean | undefined {
|
|
376
|
+
if (settings.parameterSettings?.[k]?.type === 'stateful') {
|
|
377
|
+
const statefulSetting = settings.parameterSettings[k] as ParsedStatefulParameterSetting;
|
|
378
|
+
|
|
379
|
+
if (statefulSetting.nestedSettings.type === 'array') {
|
|
380
|
+
return (statefulSetting.nestedSettings as ArrayParameterSetting).filterInStatelessMode
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (settings.parameterSettings?.[k]?.type === 'array') {
|
|
385
|
+
return (settings.parameterSettings?.[k] as ArrayParameterSetting).filterInStatelessMode;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return undefined;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function isArrayParameterWithFiltering(k: string, v: T[keyof T]): boolean {
|
|
392
|
+
const filterParameter = getFilterParameter(k);
|
|
393
|
+
|
|
394
|
+
if (settings.parameterSettings?.[k]?.type === 'stateful') {
|
|
395
|
+
const statefulSetting = settings.parameterSettings[k] as ParsedStatefulParameterSetting;
|
|
396
|
+
return statefulSetting.nestedSettings.type === 'array' &&
|
|
397
|
+
(filterParameter ?? true)
|
|
398
|
+
&& Array.isArray(v);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return settings.parameterSettings?.[k]?.type === 'array'
|
|
402
|
+
&& (filterParameter ?? true)
|
|
403
|
+
&& Array.isArray(v);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// For stateless mode, we must filter the current array so that the diff algorithm will not detect any deletes
|
|
407
|
+
function filterArrayParameterForStatelessMode(k: string, v: unknown[]): unknown[] {
|
|
408
|
+
const desiredArray = desired![k] as unknown[];
|
|
409
|
+
const matcher = settings.parameterSettings![k]!.type === 'stateful'
|
|
410
|
+
? ((settings.parameterSettings![k] as ParsedStatefulParameterSetting)
|
|
411
|
+
.nestedSettings as ParsedArrayParameterSetting)
|
|
412
|
+
.isElementEqual
|
|
413
|
+
: (settings.parameterSettings![k] as ParsedArrayParameterSetting)
|
|
414
|
+
.isElementEqual
|
|
415
|
+
|
|
416
|
+
const desiredCopy = [...desiredArray];
|
|
417
|
+
const currentCopy = [...v];
|
|
418
|
+
|
|
419
|
+
const defaultFilterMethod = ((desired: any[], current: any[]) => {
|
|
420
|
+
const result = [];
|
|
421
|
+
|
|
422
|
+
for (let counter = desired.length - 1; counter >= 0; counter--) {
|
|
423
|
+
const idx = currentCopy.findIndex((e2) => matcher(desired[counter], e2))
|
|
424
|
+
|
|
425
|
+
if (idx === -1) {
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
desired.splice(counter, 1)
|
|
430
|
+
const [element] = current.splice(idx, 1)
|
|
431
|
+
result.push(element)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return result;
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
const filterParameter = getFilterParameter(k);
|
|
438
|
+
return typeof filterParameter === 'function'
|
|
439
|
+
? filterParameter(desiredCopy, currentCopy)
|
|
440
|
+
: defaultFilterMethod(desiredCopy, currentCopy);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function filterArrayParameterForDeletes(k: string, v: unknown[]): unknown[] {
|
|
444
|
+
const stateArray = state![k] as unknown[];
|
|
445
|
+
const matcher = settings.parameterSettings![k]!.type === 'stateful'
|
|
446
|
+
? ((settings.parameterSettings![k] as ParsedStatefulParameterSetting)
|
|
447
|
+
.nestedSettings as ParsedArrayParameterSetting)
|
|
448
|
+
.isElementEqual
|
|
449
|
+
: (settings.parameterSettings![k] as ParsedArrayParameterSetting)
|
|
450
|
+
.isElementEqual
|
|
451
|
+
|
|
452
|
+
const stateCopy = [...stateArray];
|
|
453
|
+
const currentCopy = [...v];
|
|
454
|
+
|
|
455
|
+
const defaultFilterMethod = ((state: any[], current: any[]) => {
|
|
456
|
+
const result = [];
|
|
457
|
+
|
|
458
|
+
for (let counter = state.length - 1; counter >= 0; counter--) {
|
|
459
|
+
const idx = currentCopy.findIndex((e2) => matcher(state[counter], e2))
|
|
460
|
+
|
|
461
|
+
if (idx === -1) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
state.splice(counter, 1)
|
|
466
|
+
const [element] = current.splice(idx, 1)
|
|
467
|
+
result.push(element)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return result;
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
const filterParameter = getFilterParameter(k);
|
|
475
|
+
return typeof filterParameter === 'function'
|
|
476
|
+
? filterParameter(stateCopy, currentCopy)
|
|
477
|
+
: defaultFilterMethod(stateCopy, currentCopy);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// TODO: This needs to be revisited. I don't think this is valid anymore.
|
|
482
|
+
// 1. For all scenarios, there shouldn't be an apply without a plan beforehand
|
|
483
|
+
|
|
484
|
+
requiresChanges(): boolean {
|
|
485
|
+
return this.changeSet.operation !== ResourceOperation.NOOP;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** Convert the plan to a JSON response object */
|
|
489
|
+
toResponse(): PlanResponseData {
|
|
490
|
+
return {
|
|
491
|
+
planId: this.id,
|
|
492
|
+
operation: this.changeSet.operation,
|
|
493
|
+
isStateful: this.isStateful,
|
|
494
|
+
resourceName: this.coreParameters.name,
|
|
495
|
+
resourceType: this.coreParameters.type,
|
|
496
|
+
parameters: this.changeSet.parameterChanges,
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|