@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
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
|
|
2
|
+
|
|
3
|
+
import { ParsedParameterSetting } from '../resource/parsed-resource-settings.js';
|
|
4
|
+
import { ResourceSettings } from '../resource/resource-settings.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A parameter change describes a parameter level change to a resource.
|
|
8
|
+
*/
|
|
9
|
+
export interface ParameterChange<T extends StringIndexedObject> {
|
|
10
|
+
/**
|
|
11
|
+
* The name of the parameter
|
|
12
|
+
*/
|
|
13
|
+
name: keyof T & string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The operation to be performed on the parameter.
|
|
17
|
+
*/
|
|
18
|
+
operation: ParameterOperation;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The previous value of the resource (the current value on the system)
|
|
22
|
+
*/
|
|
23
|
+
previousValue: any | null;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The new value of the resource (the desired value)
|
|
27
|
+
*/
|
|
28
|
+
newValue: any | null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Whether the parameter is sensitive
|
|
32
|
+
*/
|
|
33
|
+
isSensitive: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Change set will coerce undefined values to null because undefined is not valid JSON
|
|
37
|
+
export class ChangeSet<T extends StringIndexedObject> {
|
|
38
|
+
operation: ResourceOperation
|
|
39
|
+
parameterChanges: Array<ParameterChange<T>>
|
|
40
|
+
|
|
41
|
+
constructor(
|
|
42
|
+
operation: ResourceOperation,
|
|
43
|
+
parameterChanges: Array<ParameterChange<T>>
|
|
44
|
+
) {
|
|
45
|
+
this.operation = operation;
|
|
46
|
+
this.parameterChanges = parameterChanges;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get desiredParameters(): T {
|
|
50
|
+
return this.parameterChanges
|
|
51
|
+
.reduce((obj, pc) => ({
|
|
52
|
+
...obj,
|
|
53
|
+
[pc.name]: pc.newValue,
|
|
54
|
+
}), {}) as T;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get currentParameters(): T {
|
|
58
|
+
return this.parameterChanges
|
|
59
|
+
.reduce((obj, pc) => ({
|
|
60
|
+
...obj,
|
|
61
|
+
[pc.name]: pc.previousValue,
|
|
62
|
+
}), {}) as T;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static empty<T extends StringIndexedObject>(): ChangeSet<T> {
|
|
66
|
+
return new ChangeSet<T>(ResourceOperation.NOOP, []);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static create<T extends StringIndexedObject>(desired: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T> {
|
|
70
|
+
const parameterChanges = Object.entries(desired)
|
|
71
|
+
.map(([k, v]) => ({
|
|
72
|
+
name: k,
|
|
73
|
+
operation: ParameterOperation.ADD,
|
|
74
|
+
previousValue: null,
|
|
75
|
+
newValue: v ?? null,
|
|
76
|
+
isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
|
|
77
|
+
}))
|
|
78
|
+
|
|
79
|
+
return new ChangeSet(ResourceOperation.CREATE, parameterChanges);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static noop<T extends StringIndexedObject>(parameters: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T> {
|
|
83
|
+
const parameterChanges = Object.entries(parameters)
|
|
84
|
+
.map(([k, v]) => ({
|
|
85
|
+
name: k,
|
|
86
|
+
operation: ParameterOperation.NOOP,
|
|
87
|
+
previousValue: v ?? null,
|
|
88
|
+
newValue: v ?? null,
|
|
89
|
+
isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
|
|
90
|
+
}))
|
|
91
|
+
|
|
92
|
+
return new ChangeSet(ResourceOperation.NOOP, parameterChanges);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
static destroy<T extends StringIndexedObject>(current: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T> {
|
|
96
|
+
const parameterChanges = Object.entries(current)
|
|
97
|
+
.map(([k, v]) => ({
|
|
98
|
+
name: k,
|
|
99
|
+
operation: ParameterOperation.REMOVE,
|
|
100
|
+
previousValue: v ?? null,
|
|
101
|
+
newValue: null,
|
|
102
|
+
isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
|
|
103
|
+
}))
|
|
104
|
+
|
|
105
|
+
return new ChangeSet(ResourceOperation.DESTROY, parameterChanges);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static calculateModification<T extends StringIndexedObject>(
|
|
109
|
+
desired: Partial<T>,
|
|
110
|
+
current: Partial<T>,
|
|
111
|
+
parameterSettings: Partial<Record<keyof T, ParsedParameterSetting>> = {},
|
|
112
|
+
): ChangeSet<T> {
|
|
113
|
+
const pc = ChangeSet.calculateParameterChanges(desired, current, parameterSettings);
|
|
114
|
+
|
|
115
|
+
const statefulParameterKeys = new Set(
|
|
116
|
+
Object.entries(parameterSettings)
|
|
117
|
+
.filter(([, v]) => v?.type === 'stateful')
|
|
118
|
+
.map(([k]) => k)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
const resourceOperation = pc
|
|
122
|
+
.filter((change) => change.operation !== ParameterOperation.NOOP)
|
|
123
|
+
.reduce((operation: ResourceOperation, curr: ParameterChange<T>) => {
|
|
124
|
+
let newOperation: ResourceOperation;
|
|
125
|
+
if (statefulParameterKeys.has(curr.name)) {
|
|
126
|
+
newOperation = ResourceOperation.MODIFY // All stateful parameters are modify only
|
|
127
|
+
} else if (parameterSettings[curr.name]?.canModify) {
|
|
128
|
+
newOperation = ResourceOperation.MODIFY
|
|
129
|
+
} else {
|
|
130
|
+
newOperation = ResourceOperation.RECREATE; // Default to Re-create. Should handle the majority of use cases
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return ChangeSet.combineResourceOperations(operation, newOperation);
|
|
134
|
+
}, ResourceOperation.NOOP);
|
|
135
|
+
|
|
136
|
+
return new ChangeSet<T>(resourceOperation, pc);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Calculates the differences between the desired and current parameters,
|
|
141
|
+
* and returns a list of parameter changes that describe what needs to be added,
|
|
142
|
+
* removed, or modified to match the desired state.
|
|
143
|
+
*
|
|
144
|
+
* @param {Partial<T>} desiredParameters - The desired target state of the parameters.
|
|
145
|
+
* @param {Partial<T>} currentParameters - The current state of the parameters.
|
|
146
|
+
* @param {Partial<Record<keyof T, ParameterSetting>>} [parameterOptions] - Optional settings used when comparing parameters.
|
|
147
|
+
* @return {ParameterChange<T>[]} A list of changes required to transition from the current state to the desired state.
|
|
148
|
+
*/
|
|
149
|
+
private static calculateParameterChanges<T extends StringIndexedObject>(
|
|
150
|
+
desiredParameters: Partial<T>,
|
|
151
|
+
currentParameters: Partial<T>,
|
|
152
|
+
parameterOptions?: Partial<Record<keyof T, ParsedParameterSetting>>,
|
|
153
|
+
): ParameterChange<T>[] {
|
|
154
|
+
const parameterChangeSet = new Array<ParameterChange<T>>();
|
|
155
|
+
|
|
156
|
+
// Filter out null and undefined values or else the diff below will not work
|
|
157
|
+
const desired = Object.fromEntries(
|
|
158
|
+
Object.entries(desiredParameters).filter(([, v]) => v !== null && v !== undefined)
|
|
159
|
+
) as Partial<T>
|
|
160
|
+
|
|
161
|
+
const current = Object.fromEntries(
|
|
162
|
+
Object.entries(currentParameters).filter(([, v]) => v !== null && v !== undefined)
|
|
163
|
+
) as Partial<T>
|
|
164
|
+
|
|
165
|
+
for (const k of new Set([...Object.keys(current), ...Object.keys(desired)])) {
|
|
166
|
+
if (ChangeSet.isSame(desired[k], current[k], parameterOptions?.[k])) {
|
|
167
|
+
parameterChangeSet.push({
|
|
168
|
+
name: k,
|
|
169
|
+
previousValue: current[k] ?? null,
|
|
170
|
+
newValue: desired[k] ?? null,
|
|
171
|
+
operation: ParameterOperation.NOOP,
|
|
172
|
+
isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if ((desired?.[k] === null || desired?.[k] === undefined) && (current?.[k] !== null && current?.[k] !== undefined)) {
|
|
179
|
+
parameterChangeSet.push({
|
|
180
|
+
name: k,
|
|
181
|
+
previousValue: current[k] ?? null,
|
|
182
|
+
newValue: null,
|
|
183
|
+
operation: ParameterOperation.REMOVE,
|
|
184
|
+
isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if ((current?.[k] === null || current?.[k] === undefined) && (desired?.[k] !== null && desired?.[k] !== undefined)) {
|
|
191
|
+
parameterChangeSet.push({
|
|
192
|
+
name: k,
|
|
193
|
+
previousValue: null,
|
|
194
|
+
newValue: desired[k] ?? null,
|
|
195
|
+
operation: ParameterOperation.ADD,
|
|
196
|
+
isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
parameterChangeSet.push({
|
|
203
|
+
name: k,
|
|
204
|
+
previousValue: current[k] ?? null,
|
|
205
|
+
newValue: desired[k] ?? null,
|
|
206
|
+
operation: ParameterOperation.MODIFY,
|
|
207
|
+
isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return parameterChangeSet;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private static combineResourceOperations(prev: ResourceOperation, next: ResourceOperation) {
|
|
215
|
+
const orderOfOperations = [
|
|
216
|
+
ResourceOperation.NOOP,
|
|
217
|
+
ResourceOperation.MODIFY,
|
|
218
|
+
ResourceOperation.RECREATE,
|
|
219
|
+
ResourceOperation.CREATE,
|
|
220
|
+
ResourceOperation.DESTROY,
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
const indexPrev = orderOfOperations.indexOf(prev);
|
|
224
|
+
const indexNext = orderOfOperations.indexOf(next);
|
|
225
|
+
|
|
226
|
+
return orderOfOperations[Math.max(indexPrev, indexNext)];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private static isSame(
|
|
230
|
+
desired: unknown,
|
|
231
|
+
current: unknown,
|
|
232
|
+
setting?: ParsedParameterSetting,
|
|
233
|
+
): boolean {
|
|
234
|
+
return (setting?.isEqual ?? ((a: unknown, b: unknown) => a === b))(desired, current)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { StringIndexedObject } from 'codify-schemas';
|
|
2
|
+
|
|
3
|
+
import { Plan } from './plan.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A narrower type for plans for CREATE operations. Only desiredConfig is not null.
|
|
7
|
+
*/
|
|
8
|
+
export interface CreatePlan<T extends StringIndexedObject> extends Plan<T> {
|
|
9
|
+
desiredConfig: T;
|
|
10
|
+
currentConfig: null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A narrower type for plans for DESTROY operations. Only currentConfig is not null.
|
|
15
|
+
*/
|
|
16
|
+
export interface DestroyPlan<T extends StringIndexedObject> extends Plan<T> {
|
|
17
|
+
desiredConfig: null;
|
|
18
|
+
currentConfig: T;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A narrower type for plans for MODIFY and RE-CREATE operations.
|
|
23
|
+
*/
|
|
24
|
+
export interface ModifyPlan<T extends StringIndexedObject> extends Plan<T> {
|
|
25
|
+
desiredConfig: T;
|
|
26
|
+
currentConfig: T;
|
|
27
|
+
}
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Plan } from './plan.js';
|
|
3
|
+
import { OS, ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
4
|
+
import { TestConfig, TestResource } from '../utils/test-utils.test.js';
|
|
5
|
+
import { ResourceController } from '../resource/resource-controller.js';
|
|
6
|
+
import { ParsedResourceSettings } from '../resource/parsed-resource-settings.js';
|
|
7
|
+
import { ResourceSettings } from '../resource/resource-settings.js';
|
|
8
|
+
|
|
9
|
+
describe('Plan entity tests', () => {
|
|
10
|
+
it('Adds default values properly when plan is parsed from request (Create)', () => {
|
|
11
|
+
const resource = createTestResource()
|
|
12
|
+
const controller = new ResourceController(resource);
|
|
13
|
+
|
|
14
|
+
const plan = Plan.fromResponse({
|
|
15
|
+
operation: ResourceOperation.CREATE,
|
|
16
|
+
resourceType: 'type',
|
|
17
|
+
parameters: [{
|
|
18
|
+
name: 'propB',
|
|
19
|
+
operation: ParameterOperation.ADD,
|
|
20
|
+
previousValue: null,
|
|
21
|
+
newValue: 'propBValue'
|
|
22
|
+
}],
|
|
23
|
+
isStateful: false,
|
|
24
|
+
}, controller.parsedSettings.defaultValues);
|
|
25
|
+
|
|
26
|
+
expect(plan.currentConfig).to.be.null;
|
|
27
|
+
|
|
28
|
+
expect(plan.desiredConfig).toMatchObject({
|
|
29
|
+
propA: 'defaultA',
|
|
30
|
+
propB: 'propBValue',
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
expect(plan.changeSet.parameterChanges
|
|
34
|
+
.every((pc) => pc.operation === ParameterOperation.ADD)
|
|
35
|
+
).to.be.true;
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('Adds default values properly when plan is parsed from request (Destroy)', () => {
|
|
39
|
+
const resource = createTestResource()
|
|
40
|
+
const controller = new ResourceController(resource);
|
|
41
|
+
|
|
42
|
+
const plan = Plan.fromResponse({
|
|
43
|
+
operation: ResourceOperation.DESTROY,
|
|
44
|
+
resourceType: 'type',
|
|
45
|
+
parameters: [{
|
|
46
|
+
name: 'propB',
|
|
47
|
+
operation: ParameterOperation.REMOVE,
|
|
48
|
+
previousValue: 'propBValue',
|
|
49
|
+
newValue: null,
|
|
50
|
+
}],
|
|
51
|
+
isStateful: false,
|
|
52
|
+
}, controller.parsedSettings.defaultValues);
|
|
53
|
+
|
|
54
|
+
expect(plan.currentConfig).toMatchObject({
|
|
55
|
+
propA: 'defaultA',
|
|
56
|
+
propB: 'propBValue',
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
expect(plan.desiredConfig).to.be.null;
|
|
60
|
+
|
|
61
|
+
expect(plan.changeSet.parameterChanges
|
|
62
|
+
.every((pc) => pc.operation === ParameterOperation.REMOVE)
|
|
63
|
+
).to.be.true;
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('Adds default values properly when plan is parsed from request (No-op)', () => {
|
|
67
|
+
const resource = createTestResource()
|
|
68
|
+
const controller = new ResourceController(resource);
|
|
69
|
+
|
|
70
|
+
const plan = Plan.fromResponse({
|
|
71
|
+
operation: ResourceOperation.NOOP,
|
|
72
|
+
resourceType: 'type',
|
|
73
|
+
parameters: [{
|
|
74
|
+
name: 'propB',
|
|
75
|
+
operation: ParameterOperation.NOOP,
|
|
76
|
+
previousValue: 'propBValue',
|
|
77
|
+
newValue: 'propBValue',
|
|
78
|
+
}],
|
|
79
|
+
isStateful: false,
|
|
80
|
+
}, controller.parsedSettings.defaultValues);
|
|
81
|
+
|
|
82
|
+
expect(plan.currentConfig).toMatchObject({
|
|
83
|
+
propA: 'defaultA',
|
|
84
|
+
propB: 'propBValue',
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
expect(plan.desiredConfig).toMatchObject({
|
|
88
|
+
propA: 'defaultA',
|
|
89
|
+
propB: 'propBValue',
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
expect(plan.changeSet.parameterChanges
|
|
93
|
+
.every((pc) => pc.operation === ParameterOperation.NOOP)
|
|
94
|
+
).to.be.true;
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('Does not add default value if a value has already been specified', () => {
|
|
98
|
+
const resource = createTestResource()
|
|
99
|
+
const controller = new ResourceController(resource);
|
|
100
|
+
|
|
101
|
+
const plan = Plan.fromResponse({
|
|
102
|
+
operation: ResourceOperation.CREATE,
|
|
103
|
+
resourceType: 'type',
|
|
104
|
+
parameters: [{
|
|
105
|
+
name: 'propB',
|
|
106
|
+
operation: ParameterOperation.ADD,
|
|
107
|
+
previousValue: null,
|
|
108
|
+
newValue: 'propBValue',
|
|
109
|
+
}, {
|
|
110
|
+
name: 'propA',
|
|
111
|
+
operation: ParameterOperation.ADD,
|
|
112
|
+
previousValue: null,
|
|
113
|
+
newValue: 'propAValue',
|
|
114
|
+
}],
|
|
115
|
+
isStateful: false,
|
|
116
|
+
}, controller.parsedSettings.defaultValues);
|
|
117
|
+
|
|
118
|
+
expect(plan.currentConfig).to.be.null
|
|
119
|
+
|
|
120
|
+
expect(plan.desiredConfig).toMatchObject({
|
|
121
|
+
propA: 'propAValue',
|
|
122
|
+
propB: 'propBValue',
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
expect(plan.changeSet.parameterChanges
|
|
126
|
+
.every((pc) => pc.operation === ParameterOperation.ADD)
|
|
127
|
+
).to.be.true;
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('Returns the original resource names', () => {
|
|
131
|
+
const plan = Plan.calculate<TestConfig>({
|
|
132
|
+
desired: { propA: 'propA' },
|
|
133
|
+
currentArray: [{ propA: 'propA2' }],
|
|
134
|
+
state: null,
|
|
135
|
+
core: {
|
|
136
|
+
type: 'type',
|
|
137
|
+
name: 'name1'
|
|
138
|
+
},
|
|
139
|
+
settings: new ParsedResourceSettings<TestConfig>({ id: 'type' }),
|
|
140
|
+
isStateful: false,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(plan.toResponse()).toMatchObject({
|
|
144
|
+
resourceType: 'type',
|
|
145
|
+
resourceName: 'name1',
|
|
146
|
+
operation: ResourceOperation.RECREATE
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('Filters array parameters in stateless mode (by default)', async () => {
|
|
151
|
+
const resource = new class extends TestResource {
|
|
152
|
+
getSettings(): ResourceSettings<any> {
|
|
153
|
+
return {
|
|
154
|
+
id: 'type',
|
|
155
|
+
operatingSystems: [OS.Darwin],
|
|
156
|
+
parameterSettings: {
|
|
157
|
+
propZ: { type: 'array', isElementEqual: (a, b) => b.includes(a) }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async refresh(): Promise<Partial<any> | null> {
|
|
163
|
+
return {
|
|
164
|
+
propZ: [
|
|
165
|
+
'20.15.0',
|
|
166
|
+
'20.15.1'
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const controller = new ResourceController(resource);
|
|
173
|
+
const plan = await controller.plan(
|
|
174
|
+
{ type: 'type' },
|
|
175
|
+
{ propZ: ['20.15'], } as any,
|
|
176
|
+
null,
|
|
177
|
+
false
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('Filters array parameters in delete mode (when desired is null)', async () => {
|
|
184
|
+
const resource = new class extends TestResource {
|
|
185
|
+
getSettings(): ResourceSettings<any> {
|
|
186
|
+
return {
|
|
187
|
+
id: 'type',
|
|
188
|
+
operatingSystems: [OS.Darwin],
|
|
189
|
+
parameterSettings: {
|
|
190
|
+
propZ: { type: 'array', isElementEqual: (a, b) => b.includes(a) }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async refresh(): Promise<Partial<any> | null> {
|
|
196
|
+
return {
|
|
197
|
+
propZ: [
|
|
198
|
+
'20.15.0',
|
|
199
|
+
'20.15.1'
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const controller = new ResourceController(resource);
|
|
206
|
+
const plan = await controller.plan(
|
|
207
|
+
{ type: 'type' },
|
|
208
|
+
null,
|
|
209
|
+
{ propZ: ['20.15.0'], } as any,
|
|
210
|
+
true
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
214
|
+
expect(plan).toMatchObject({
|
|
215
|
+
id: expect.any(String),
|
|
216
|
+
changeSet: expect.objectContaining({
|
|
217
|
+
operation: ResourceOperation.DESTROY,
|
|
218
|
+
parameterChanges: [
|
|
219
|
+
expect.objectContaining({ operation: 'remove', name: 'propZ', previousValue: ['20.15.0'], newValue: null }),
|
|
220
|
+
],
|
|
221
|
+
}),
|
|
222
|
+
coreParameters: expect.objectContaining({
|
|
223
|
+
type: 'type',
|
|
224
|
+
}),
|
|
225
|
+
isStateful: true,
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('Doesn\'t filters array parameters if filtering is disabled', async () => {
|
|
230
|
+
const resource = new class extends TestResource {
|
|
231
|
+
getSettings(): ResourceSettings<any> {
|
|
232
|
+
return {
|
|
233
|
+
id: 'type',
|
|
234
|
+
operatingSystems: [OS.Darwin],
|
|
235
|
+
parameterSettings: {
|
|
236
|
+
propZ: {
|
|
237
|
+
type: 'array',
|
|
238
|
+
canModify: true,
|
|
239
|
+
isElementEqual: (a, b) => b.includes(a),
|
|
240
|
+
filterInStatelessMode: false
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async refresh(): Promise<Partial<any> | null> {
|
|
247
|
+
return {
|
|
248
|
+
propZ: [
|
|
249
|
+
'20.15.0',
|
|
250
|
+
'20.15.1'
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const controller = new ResourceController(resource);
|
|
257
|
+
const plan = await controller.plan(
|
|
258
|
+
{ type: 'type' },
|
|
259
|
+
{ propZ: ['20.15'], } as any,
|
|
260
|
+
null,
|
|
261
|
+
false
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
expect(plan.changeSet).toMatchObject({
|
|
265
|
+
operation: ResourceOperation.MODIFY,
|
|
266
|
+
parameterChanges: expect.arrayContaining([
|
|
267
|
+
expect.objectContaining({
|
|
268
|
+
name: 'propZ',
|
|
269
|
+
previousValue: expect.arrayContaining([
|
|
270
|
+
'20.15.0',
|
|
271
|
+
'20.15.1'
|
|
272
|
+
]),
|
|
273
|
+
newValue: expect.arrayContaining([
|
|
274
|
+
'20.15'
|
|
275
|
+
]),
|
|
276
|
+
operation: 'modify'
|
|
277
|
+
})
|
|
278
|
+
])
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('Can use the requiredParameters to match the correct resources together', async () => {
|
|
283
|
+
const resource1 = new class extends TestResource {
|
|
284
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
285
|
+
return {
|
|
286
|
+
id: 'type',
|
|
287
|
+
operatingSystems: [OS.Darwin],
|
|
288
|
+
parameterSettings: {
|
|
289
|
+
propA: { type: 'string' },
|
|
290
|
+
propB: { type: 'string', canModify: true },
|
|
291
|
+
},
|
|
292
|
+
allowMultiple: {
|
|
293
|
+
identifyingParameters: ['propA']
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async refresh(): Promise<Partial<any> | null> {
|
|
299
|
+
return [{
|
|
300
|
+
propA: 'same',
|
|
301
|
+
propB: 'old',
|
|
302
|
+
}, {
|
|
303
|
+
propA: 'different',
|
|
304
|
+
propB: 'different',
|
|
305
|
+
}]
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const controller = new ResourceController(resource1);
|
|
310
|
+
const plan = await controller.plan(
|
|
311
|
+
{ type: 'type' },
|
|
312
|
+
{ propA: 'same', propB: 'new' },
|
|
313
|
+
null,
|
|
314
|
+
false
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
expect(plan.changeSet).toMatchObject({
|
|
318
|
+
operation: ResourceOperation.MODIFY,
|
|
319
|
+
parameterChanges: expect.arrayContaining([
|
|
320
|
+
expect.objectContaining({
|
|
321
|
+
name: 'propA',
|
|
322
|
+
previousValue: 'same',
|
|
323
|
+
newValue: 'same',
|
|
324
|
+
operation: 'noop'
|
|
325
|
+
}),
|
|
326
|
+
expect.objectContaining({
|
|
327
|
+
name: 'propB',
|
|
328
|
+
previousValue: 'old',
|
|
329
|
+
newValue: 'new',
|
|
330
|
+
operation: 'modify'
|
|
331
|
+
})
|
|
332
|
+
])
|
|
333
|
+
})
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('Can use the schema to determine required parameters for multiple allowed', async () => {
|
|
337
|
+
const resource1 = new class extends TestResource {
|
|
338
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
339
|
+
return {
|
|
340
|
+
id: 'type',
|
|
341
|
+
operatingSystems: [OS.Darwin],
|
|
342
|
+
parameterSettings: {
|
|
343
|
+
propA: { type: 'string' },
|
|
344
|
+
propB: { type: 'string', canModify: true },
|
|
345
|
+
},
|
|
346
|
+
allowMultiple: true,
|
|
347
|
+
schema: {
|
|
348
|
+
'$schema': 'http://json-schema.org/draft-07/schema',
|
|
349
|
+
'$id': 'https://www.codifycli.com/type.json',
|
|
350
|
+
'type': 'object',
|
|
351
|
+
'properties': {
|
|
352
|
+
propA: { type: 'string' },
|
|
353
|
+
propB: { type: 'string' }
|
|
354
|
+
},
|
|
355
|
+
required: ['propA']
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async refresh(): Promise<Partial<any> | null> {
|
|
361
|
+
return [{
|
|
362
|
+
propA: 'same',
|
|
363
|
+
propB: 'old',
|
|
364
|
+
}, {
|
|
365
|
+
propA: 'different',
|
|
366
|
+
propB: 'different',
|
|
367
|
+
}]
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const controller = new ResourceController(resource1);
|
|
372
|
+
const plan = await controller.plan(
|
|
373
|
+
{ type: 'type' },
|
|
374
|
+
{ propA: 'same', propB: 'new' },
|
|
375
|
+
null,
|
|
376
|
+
false
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
expect(plan.changeSet).toMatchObject({
|
|
380
|
+
operation: ResourceOperation.MODIFY,
|
|
381
|
+
parameterChanges: expect.arrayContaining([
|
|
382
|
+
expect.objectContaining({
|
|
383
|
+
name: 'propA',
|
|
384
|
+
previousValue: 'same',
|
|
385
|
+
newValue: 'same',
|
|
386
|
+
operation: 'noop'
|
|
387
|
+
}),
|
|
388
|
+
expect.objectContaining({
|
|
389
|
+
name: 'propB',
|
|
390
|
+
previousValue: 'old',
|
|
391
|
+
newValue: 'new',
|
|
392
|
+
operation: 'modify'
|
|
393
|
+
})
|
|
394
|
+
])
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
function createTestResource() {
|
|
400
|
+
return new class extends TestResource {
|
|
401
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
402
|
+
return {
|
|
403
|
+
id: 'type',
|
|
404
|
+
operatingSystems: [OS.Darwin],
|
|
405
|
+
parameterSettings: {
|
|
406
|
+
propA: {
|
|
407
|
+
default: 'defaultA'
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|