codify-plugin-lib 1.0.81 → 1.0.83
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/plan/change-set.js +1 -10
- package/dist/plan/plan.js +15 -2
- package/dist/resource/parsed-resource-settings.d.ts +0 -1
- package/dist/resource/parsed-resource-settings.js +2 -12
- package/dist/resource/resource-controller.js +4 -4
- package/dist/resource/resource-settings.d.ts +1 -1
- package/dist/resource/resource-settings.js +11 -2
- package/dist/resource/stateful-parameter.d.ts +2 -1
- package/package.json +2 -2
- package/src/plan/change-set.test.ts +83 -23
- package/src/plan/change-set.ts +2 -12
- package/src/plan/plan.ts +19 -4
- package/src/resource/parsed-resource-settings.ts +2 -22
- package/src/resource/resource-controller.ts +4 -3
- package/src/resource/resource-settings.ts +15 -2
- package/src/resource/stateful-parameter.test.ts +50 -3
- package/src/resource/stateful-parameter.ts +2 -1
package/dist/plan/change-set.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
2
|
-
import { areArraysEqual } from '../utils/utils.js';
|
|
3
2
|
// Change set will coerce undefined values to null because undefined is not valid JSON
|
|
4
3
|
export class ChangeSet {
|
|
5
4
|
operation;
|
|
@@ -139,14 +138,6 @@ export class ChangeSet {
|
|
|
139
138
|
return orderOfOperations[Math.max(indexPrev, indexNext)];
|
|
140
139
|
}
|
|
141
140
|
static isSame(desired, current, setting) {
|
|
142
|
-
|
|
143
|
-
case 'array': {
|
|
144
|
-
const arrayParameter = setting;
|
|
145
|
-
return areArraysEqual(arrayParameter, desired, current);
|
|
146
|
-
}
|
|
147
|
-
default: {
|
|
148
|
-
return (setting?.isEqual ?? ((a, b) => a === b))(desired, current);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
141
|
+
return (setting?.isEqual ?? ((a, b) => a === b))(desired, current);
|
|
151
142
|
}
|
|
152
143
|
}
|
package/dist/plan/plan.js
CHANGED
|
@@ -148,13 +148,26 @@ export class Plan {
|
|
|
148
148
|
&& settings.parameterSettings[k].definition.getSettings().type === 'array'
|
|
149
149
|
&& Array.isArray(v);
|
|
150
150
|
}
|
|
151
|
+
// For stateless mode, we must filter the current array so that the diff algorithm will not detect any deletes
|
|
151
152
|
function filterArrayStatefulParameter(k, v) {
|
|
152
153
|
const desiredArray = desired[k];
|
|
153
154
|
const matcher = settings.parameterSettings[k]
|
|
154
155
|
.definition
|
|
155
156
|
.getSettings()
|
|
156
|
-
.isElementEqual;
|
|
157
|
-
|
|
157
|
+
.isElementEqual ?? ((a, b) => a === b);
|
|
158
|
+
const desiredCopy = [...desiredArray];
|
|
159
|
+
const currentCopy = [...v];
|
|
160
|
+
const result = [];
|
|
161
|
+
for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
|
|
162
|
+
const idx = currentCopy.findIndex((e2) => matcher(desiredCopy[counter], e2));
|
|
163
|
+
if (idx === -1) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
desiredCopy.splice(counter, 1);
|
|
167
|
+
const [element] = currentCopy.splice(idx, 1);
|
|
168
|
+
result.push(element);
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
158
171
|
}
|
|
159
172
|
}
|
|
160
173
|
// TODO: This needs to be revisited. I don't think this is valid anymore.
|
|
@@ -21,6 +21,5 @@ export declare class ParsedResourceSettings<T extends StringIndexedObject> imple
|
|
|
21
21
|
get statefulParameterOrder(): Map<keyof T, number>;
|
|
22
22
|
private validateSettings;
|
|
23
23
|
private validateParameterEqualsFn;
|
|
24
|
-
private resolveEqualsFn;
|
|
25
24
|
private getFromCacheOrCreate;
|
|
26
25
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ParameterEqualsDefaults } from './resource-settings.js';
|
|
1
|
+
import { resolveEqualsFn } from './resource-settings.js';
|
|
3
2
|
export class ParsedResourceSettings {
|
|
4
3
|
cache = new Map();
|
|
5
4
|
id;
|
|
@@ -35,7 +34,7 @@ export class ParsedResourceSettings {
|
|
|
35
34
|
const settings = Object.entries(this.settings.parameterSettings ?? {})
|
|
36
35
|
.map(([k, v]) => [k, v])
|
|
37
36
|
.map(([k, v]) => {
|
|
38
|
-
v.isEqual =
|
|
37
|
+
v.isEqual = resolveEqualsFn(v, k);
|
|
39
38
|
return [k, v];
|
|
40
39
|
});
|
|
41
40
|
return Object.fromEntries(settings);
|
|
@@ -106,15 +105,6 @@ export class ParsedResourceSettings {
|
|
|
106
105
|
}
|
|
107
106
|
// The rest of the types have defaults set already
|
|
108
107
|
}
|
|
109
|
-
resolveEqualsFn(parameter, key) {
|
|
110
|
-
if (parameter.type === 'array') {
|
|
111
|
-
return parameter.isEqual ?? areArraysEqual.bind(areArraysEqual, parameter);
|
|
112
|
-
}
|
|
113
|
-
if (parameter.type === 'stateful') {
|
|
114
|
-
return this.resolveEqualsFn(parameter.definition.getSettings(), key);
|
|
115
|
-
}
|
|
116
|
-
return parameter.isEqual ?? ParameterEqualsDefaults[parameter.type] ?? (((a, b) => a === b));
|
|
117
|
-
}
|
|
118
108
|
getFromCacheOrCreate(key, create) {
|
|
119
109
|
if (this.cache.has(key)) {
|
|
120
110
|
return this.cache.get(key);
|
|
@@ -73,7 +73,7 @@ export class ResourceController {
|
|
|
73
73
|
await this.applyTransformParameters(desiredConfig);
|
|
74
74
|
// Parse data from the user supplied config
|
|
75
75
|
const parsedConfig = new ConfigParser(desiredConfig, stateConfig, this.parsedSettings.statefulParameters);
|
|
76
|
-
const { coreParameters, desiredParameters, stateParameters, allNonStatefulParameters, allStatefulParameters, } = parsedConfig;
|
|
76
|
+
const { coreParameters, desiredParameters, stateParameters, allParameters, allNonStatefulParameters, allStatefulParameters, } = parsedConfig;
|
|
77
77
|
// Refresh resource parameters. This refreshes the parameters that configure the resource itself
|
|
78
78
|
const currentParametersArray = await this.refreshNonStatefulParameters(allNonStatefulParameters);
|
|
79
79
|
// Short circuit here. If the resource is non-existent, there's no point checking stateful parameters
|
|
@@ -93,7 +93,7 @@ export class ResourceController {
|
|
|
93
93
|
}
|
|
94
94
|
// Refresh stateful parameters. These parameters have state external to the resource. allowMultiple
|
|
95
95
|
// does not work together with stateful parameters
|
|
96
|
-
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters);
|
|
96
|
+
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters, allParameters);
|
|
97
97
|
return Plan.calculate({
|
|
98
98
|
desiredParameters,
|
|
99
99
|
currentParametersArray: [{ ...currentParametersArray[0], ...statefulCurrentParameters }],
|
|
@@ -220,7 +220,7 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
220
220
|
}
|
|
221
221
|
// Refresh stateful parameters
|
|
222
222
|
// This refreshes parameters that are stateful (they can be added, deleted separately from the resource)
|
|
223
|
-
async refreshStatefulParameters(statefulParametersConfig) {
|
|
223
|
+
async refreshStatefulParameters(statefulParametersConfig, allParameters) {
|
|
224
224
|
const result = {};
|
|
225
225
|
const sortedEntries = Object.entries(statefulParametersConfig)
|
|
226
226
|
.sort(([key1], [key2]) => this.parsedSettings.statefulParameterOrder.get(key1) - this.parsedSettings.statefulParameterOrder.get(key2));
|
|
@@ -229,7 +229,7 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
229
229
|
if (!statefulParameter) {
|
|
230
230
|
throw new Error(`Stateful parameter ${key} was not found`);
|
|
231
231
|
}
|
|
232
|
-
result[key] = await statefulParameter.refresh(desiredValue ?? null);
|
|
232
|
+
result[key] = await statefulParameter.refresh(desiredValue ?? null, allParameters);
|
|
233
233
|
}
|
|
234
234
|
return result;
|
|
235
235
|
}
|
|
@@ -146,4 +146,4 @@ export interface StatefulParameterSetting extends DefaultParameterSetting {
|
|
|
146
146
|
*/
|
|
147
147
|
order?: number;
|
|
148
148
|
}
|
|
149
|
-
export declare
|
|
149
|
+
export declare function resolveEqualsFn(parameter: ParameterSetting, key: string): (desired: unknown, current: unknown) => boolean;
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { untildify } from '../utils/utils.js';
|
|
3
|
-
|
|
2
|
+
import { areArraysEqual, untildify } from '../utils/utils.js';
|
|
3
|
+
const ParameterEqualsDefaults = {
|
|
4
4
|
'boolean': (a, b) => Boolean(a) === Boolean(b),
|
|
5
5
|
'directory': (a, b) => path.resolve(untildify(String(a))) === path.resolve(untildify(String(b))),
|
|
6
6
|
'number': (a, b) => Number(a) === Number(b),
|
|
7
7
|
'string': (a, b) => String(a) === String(b),
|
|
8
8
|
'version': (desired, current) => String(current).includes(String(desired))
|
|
9
9
|
};
|
|
10
|
+
export function resolveEqualsFn(parameter, key) {
|
|
11
|
+
if (parameter.type === 'array') {
|
|
12
|
+
return parameter.isEqual ?? areArraysEqual.bind(areArraysEqual, parameter);
|
|
13
|
+
}
|
|
14
|
+
if (parameter.type === 'stateful') {
|
|
15
|
+
return resolveEqualsFn(parameter.definition.getSettings(), key);
|
|
16
|
+
}
|
|
17
|
+
return parameter.isEqual ?? ParameterEqualsDefaults[parameter.type] ?? (((a, b) => a === b));
|
|
18
|
+
}
|
|
@@ -25,10 +25,11 @@ export declare abstract class StatefulParameter<T extends StringIndexedObject, V
|
|
|
25
25
|
* Return the value of the stateful parameter or null if not found.
|
|
26
26
|
*
|
|
27
27
|
* @param desired The desired value of the user.
|
|
28
|
+
* @param config The desired config
|
|
28
29
|
*
|
|
29
30
|
* @return The value of the stateful parameter currently on the system or null if not found
|
|
30
31
|
*/
|
|
31
|
-
abstract refresh(desired: V | null): Promise<V | null>;
|
|
32
|
+
abstract refresh(desired: V | null, config: Partial<T>): Promise<V | null>;
|
|
32
33
|
/**
|
|
33
34
|
* Create the stateful parameter on the system. This method is similar {@link Resource.create} except that its only
|
|
34
35
|
* applicable to the stateful parameter. For resource `CREATE` operations, this method will be called after the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codify-plugin-lib",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.83",
|
|
4
4
|
"description": "Library plugin library",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@oclif/prettier-config": "^0.2.1",
|
|
23
23
|
"@oclif/test": "^3",
|
|
24
24
|
"@types/npmcli__promise-spawn": "^6.0.3",
|
|
25
|
-
"@types/node": "^
|
|
25
|
+
"@types/node": "^20",
|
|
26
26
|
"@types/semver": "^7.5.4",
|
|
27
27
|
"@types/sinon": "^17.0.3",
|
|
28
28
|
"@types/uuid": "^10.0.0",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ChangeSet } from './change-set.js';
|
|
2
2
|
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
3
3
|
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { ParsedResourceSettings } from '../resource/parsed-resource-settings.js';
|
|
4
5
|
|
|
5
6
|
describe('Change set tests', () => {
|
|
6
7
|
it ('Correctly diffs two resource configs (modify)', () => {
|
|
@@ -31,7 +32,7 @@ describe('Change set tests', () => {
|
|
|
31
32
|
propA: 'after',
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
const cs = ChangeSet.calculateModification(after, before
|
|
35
|
+
const cs = ChangeSet.calculateModification(after, before);
|
|
35
36
|
expect(cs.parameterChanges.length).to.eq(2);
|
|
36
37
|
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
37
38
|
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.ADD);
|
|
@@ -104,7 +105,14 @@ describe('Change set tests', () => {
|
|
|
104
105
|
propA: ['b', 'a', 'c'],
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
const
|
|
108
|
+
const parameterSettings = new ParsedResourceSettings({
|
|
109
|
+
id: 'type',
|
|
110
|
+
parameterSettings: {
|
|
111
|
+
propA: { type: 'array' }
|
|
112
|
+
}
|
|
113
|
+
}).parameterSettings
|
|
114
|
+
|
|
115
|
+
const cs = ChangeSet.calculateModification(after, before, parameterSettings);
|
|
108
116
|
expect(cs.parameterChanges.length).to.eq(1);
|
|
109
117
|
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
|
|
110
118
|
expect(cs.operation).to.eq(ResourceOperation.NOOP)
|
|
@@ -119,7 +127,14 @@ describe('Change set tests', () => {
|
|
|
119
127
|
propA: ['b', 'a'],
|
|
120
128
|
}
|
|
121
129
|
|
|
122
|
-
const
|
|
130
|
+
const parameterSettings = new ParsedResourceSettings({
|
|
131
|
+
id: 'type',
|
|
132
|
+
parameterSettings: {
|
|
133
|
+
propA: { type: 'array' }
|
|
134
|
+
}
|
|
135
|
+
}).parameterSettings
|
|
136
|
+
|
|
137
|
+
const cs = ChangeSet.calculateModification(after, before, parameterSettings);
|
|
123
138
|
expect(cs.parameterChanges.length).to.eq(1);
|
|
124
139
|
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
125
140
|
expect(cs.operation).to.eq(ResourceOperation.RECREATE)
|
|
@@ -135,7 +150,14 @@ describe('Change set tests', () => {
|
|
|
135
150
|
propB: 'before'
|
|
136
151
|
}
|
|
137
152
|
|
|
138
|
-
const
|
|
153
|
+
const parameterSettings = new ParsedResourceSettings({
|
|
154
|
+
id: 'type',
|
|
155
|
+
parameterSettings: {
|
|
156
|
+
propA: { canModify: true }
|
|
157
|
+
}
|
|
158
|
+
}).parameterSettings
|
|
159
|
+
|
|
160
|
+
const cs = ChangeSet.calculateModification(after, before, parameterSettings);
|
|
139
161
|
expect(cs.parameterChanges.length).to.eq(2);
|
|
140
162
|
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
141
163
|
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.REMOVE);
|
|
@@ -152,10 +174,15 @@ describe('Change set tests', () => {
|
|
|
152
174
|
propB: 'before'
|
|
153
175
|
}
|
|
154
176
|
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
const parameterSettings = new ParsedResourceSettings({
|
|
178
|
+
id: 'type',
|
|
179
|
+
parameterSettings: {
|
|
180
|
+
propA: { canModify: true },
|
|
181
|
+
propB: { canModify: true }
|
|
182
|
+
},
|
|
183
|
+
}).parameterSettings
|
|
184
|
+
|
|
185
|
+
const cs = ChangeSet.calculateModification<any>(after, before, parameterSettings);
|
|
159
186
|
expect(cs.parameterChanges.length).to.eq(2);
|
|
160
187
|
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
161
188
|
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.REMOVE);
|
|
@@ -167,7 +194,15 @@ describe('Change set tests', () => {
|
|
|
167
194
|
const arrA = ['a', 'b', 'd'];
|
|
168
195
|
const arrB = ['a', 'b', 'd'];
|
|
169
196
|
|
|
170
|
-
const
|
|
197
|
+
const parameterSettings = new ParsedResourceSettings({
|
|
198
|
+
id: 'type',
|
|
199
|
+
parameterSettings: {
|
|
200
|
+
propA: { type: 'array' }
|
|
201
|
+
},
|
|
202
|
+
}).parameterSettings
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, parameterSettings)
|
|
171
206
|
|
|
172
207
|
expect(result.operation).to.eq(ResourceOperation.NOOP);
|
|
173
208
|
})
|
|
@@ -176,7 +211,14 @@ describe('Change set tests', () => {
|
|
|
176
211
|
const arrA = ['a', 'b'];
|
|
177
212
|
const arrB = ['a', 'b', 'd'];
|
|
178
213
|
|
|
179
|
-
const
|
|
214
|
+
const parameterSettings = new ParsedResourceSettings({
|
|
215
|
+
id: 'type',
|
|
216
|
+
parameterSettings: {
|
|
217
|
+
propA: { type: 'array' }
|
|
218
|
+
},
|
|
219
|
+
}).parameterSettings
|
|
220
|
+
|
|
221
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, parameterSettings)
|
|
180
222
|
|
|
181
223
|
expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
182
224
|
})
|
|
@@ -185,7 +227,14 @@ describe('Change set tests', () => {
|
|
|
185
227
|
const arrA = ['b', 'a', 'd'];
|
|
186
228
|
const arrB = ['a', 'b', 'd'];
|
|
187
229
|
|
|
188
|
-
const
|
|
230
|
+
const parameterSettings = new ParsedResourceSettings({
|
|
231
|
+
id: 'type',
|
|
232
|
+
parameterSettings: {
|
|
233
|
+
propA: { type: 'array' }
|
|
234
|
+
},
|
|
235
|
+
}).parameterSettings
|
|
236
|
+
|
|
237
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, parameterSettings)
|
|
189
238
|
|
|
190
239
|
expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
|
|
191
240
|
})
|
|
@@ -194,12 +243,18 @@ describe('Change set tests', () => {
|
|
|
194
243
|
const arrA = [{ key1: 'a' }, { key1: 'a' }, { key1: 'a' }];
|
|
195
244
|
const arrB = [{ key1: 'a' }, { key1: 'a' }, { key1: 'b' }];
|
|
196
245
|
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
246
|
+
const parameterSettings = new ParsedResourceSettings({
|
|
247
|
+
id: 'type',
|
|
248
|
+
parameterSettings: {
|
|
249
|
+
propA: {
|
|
250
|
+
type: 'array',
|
|
251
|
+
isElementEqual: (a, b) => a.key1 === b.key1
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
}).parameterSettings
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, parameterSettings)
|
|
203
258
|
|
|
204
259
|
expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
205
260
|
})
|
|
@@ -208,12 +263,17 @@ describe('Change set tests', () => {
|
|
|
208
263
|
const arrA = [{ key1: 'b' }, { key1: 'a' }, { key1: 'a' }];
|
|
209
264
|
const arrB = [{ key1: 'a' }, { key1: 'a' }, { key1: 'b' }];
|
|
210
265
|
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
266
|
+
const parameterSettings = new ParsedResourceSettings({
|
|
267
|
+
id: 'type',
|
|
268
|
+
parameterSettings: {
|
|
269
|
+
propA: {
|
|
270
|
+
type: 'array',
|
|
271
|
+
isElementEqual: (a, b) => a.key1 === b.key1
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
}).parameterSettings
|
|
275
|
+
|
|
276
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, parameterSettings)
|
|
217
277
|
|
|
218
278
|
expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
|
|
219
279
|
})
|
package/src/plan/change-set.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { areArraysEqual } from '../utils/utils.js';
|
|
3
|
+
import { ParameterSetting } from '../resource/resource-settings.js';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* A parameter change describes a parameter level change to a resource.
|
|
@@ -215,15 +214,6 @@ export class ChangeSet<T extends StringIndexedObject> {
|
|
|
215
214
|
current: unknown,
|
|
216
215
|
setting?: ParameterSetting,
|
|
217
216
|
): boolean {
|
|
218
|
-
|
|
219
|
-
case 'array': {
|
|
220
|
-
const arrayParameter = setting as ArrayParameterSetting;
|
|
221
|
-
return areArraysEqual(arrayParameter, desired, current)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
default: {
|
|
225
|
-
return (setting?.isEqual ?? ((a, b) => a === b))(desired, current)
|
|
226
|
-
}
|
|
227
|
-
}
|
|
217
|
+
return (setting?.isEqual ?? ((a, b) => a === b))(desired, current)
|
|
228
218
|
}
|
|
229
219
|
}
|
package/src/plan/plan.ts
CHANGED
|
@@ -253,16 +253,31 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
253
253
|
&& Array.isArray(v)
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
+
// For stateless mode, we must filter the current array so that the diff algorithm will not detect any deletes
|
|
256
257
|
function filterArrayStatefulParameter(k: string, v: unknown[]): unknown[] {
|
|
257
258
|
const desiredArray = desired![k] as unknown[];
|
|
258
259
|
const matcher = ((settings.parameterSettings![k] as StatefulParameterSetting)
|
|
259
260
|
.definition
|
|
260
261
|
.getSettings() as ArrayParameterSetting)
|
|
261
|
-
.isElementEqual;
|
|
262
|
+
.isElementEqual ?? ((a, b) => a === b);
|
|
262
263
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
264
|
+
const desiredCopy = [...desiredArray];
|
|
265
|
+
const currentCopy = [...v];
|
|
266
|
+
const result = [];
|
|
267
|
+
|
|
268
|
+
for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
|
|
269
|
+
const idx = currentCopy.findIndex((e2) => matcher(desiredCopy[counter], e2))
|
|
270
|
+
|
|
271
|
+
if (idx === -1) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
desiredCopy.splice(counter, 1)
|
|
276
|
+
const [element] = currentCopy.splice(idx, 1)
|
|
277
|
+
result.push(element)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return result;
|
|
266
281
|
}
|
|
267
282
|
}
|
|
268
283
|
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import { StringIndexedObject } from 'codify-schemas';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
ArrayParameterSetting,
|
|
6
|
-
ParameterEqualsDefaults,
|
|
7
|
-
ParameterSetting,
|
|
8
|
-
ParameterSettingType,
|
|
9
|
-
ResourceSettings,
|
|
10
|
-
StatefulParameterSetting
|
|
11
|
-
} from './resource-settings.js';
|
|
3
|
+
import { ParameterSetting, resolveEqualsFn, ResourceSettings, StatefulParameterSetting } from './resource-settings.js';
|
|
12
4
|
import { StatefulParameter as StatefulParameterImpl } from './stateful-parameter.js'
|
|
13
5
|
|
|
14
6
|
export class ParsedResourceSettings<T extends StringIndexedObject> implements ResourceSettings<T> {
|
|
@@ -54,7 +46,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
|
|
|
54
46
|
const settings = Object.entries(this.settings.parameterSettings ?? {})
|
|
55
47
|
.map(([k, v]) => [k, v!] as const)
|
|
56
48
|
.map(([k, v]) => {
|
|
57
|
-
v.isEqual =
|
|
49
|
+
v.isEqual = resolveEqualsFn(v, k);
|
|
58
50
|
|
|
59
51
|
return [k, v];
|
|
60
52
|
})
|
|
@@ -154,18 +146,6 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
|
|
|
154
146
|
// The rest of the types have defaults set already
|
|
155
147
|
}
|
|
156
148
|
|
|
157
|
-
private resolveEqualsFn(parameter: ParameterSetting, key: string): (desired: unknown, current: unknown) => boolean {
|
|
158
|
-
if (parameter.type === 'array') {
|
|
159
|
-
return parameter.isEqual ?? areArraysEqual.bind(areArraysEqual, parameter as ArrayParameterSetting)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (parameter.type === 'stateful') {
|
|
163
|
-
return this.resolveEqualsFn((parameter as StatefulParameterSetting).definition.getSettings(), key)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return parameter.isEqual ?? ParameterEqualsDefaults[parameter.type as ParameterSettingType] ?? (((a, b) => a === b));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
149
|
private getFromCacheOrCreate<T2>(key: string, create: () => T2): T2 {
|
|
170
150
|
if (this.cache.has(key)) {
|
|
171
151
|
return this.cache.get(key) as T2
|
|
@@ -112,6 +112,7 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
112
112
|
coreParameters,
|
|
113
113
|
desiredParameters,
|
|
114
114
|
stateParameters,
|
|
115
|
+
allParameters,
|
|
115
116
|
allNonStatefulParameters,
|
|
116
117
|
allStatefulParameters,
|
|
117
118
|
} = parsedConfig;
|
|
@@ -138,7 +139,7 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
138
139
|
|
|
139
140
|
// Refresh stateful parameters. These parameters have state external to the resource. allowMultiple
|
|
140
141
|
// does not work together with stateful parameters
|
|
141
|
-
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters);
|
|
142
|
+
const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters, allParameters);
|
|
142
143
|
|
|
143
144
|
return Plan.calculate({
|
|
144
145
|
desiredParameters,
|
|
@@ -298,7 +299,7 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
298
299
|
|
|
299
300
|
// Refresh stateful parameters
|
|
300
301
|
// This refreshes parameters that are stateful (they can be added, deleted separately from the resource)
|
|
301
|
-
private async refreshStatefulParameters(statefulParametersConfig: Partial<T>): Promise<Partial<T>> {
|
|
302
|
+
private async refreshStatefulParameters(statefulParametersConfig: Partial<T>, allParameters: Partial<T>): Promise<Partial<T>> {
|
|
302
303
|
const result: Partial<T> = {}
|
|
303
304
|
const sortedEntries = Object.entries(statefulParametersConfig)
|
|
304
305
|
.sort(
|
|
@@ -311,7 +312,7 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
311
312
|
throw new Error(`Stateful parameter ${key} was not found`);
|
|
312
313
|
}
|
|
313
314
|
|
|
314
|
-
(result as Record<string, unknown>)[key] = await statefulParameter.refresh(desiredValue ?? null)
|
|
315
|
+
(result as Record<string, unknown>)[key] = await statefulParameter.refresh(desiredValue ?? null, allParameters)
|
|
315
316
|
}
|
|
316
317
|
|
|
317
318
|
return result;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StringIndexedObject } from 'codify-schemas';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import { untildify } from '../utils/utils.js';
|
|
4
|
+
import { areArraysEqual, untildify } from '../utils/utils.js';
|
|
5
5
|
import { StatefulParameter } from './stateful-parameter.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -183,10 +183,23 @@ export interface StatefulParameterSetting extends DefaultParameterSetting {
|
|
|
183
183
|
order?: number,
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
const ParameterEqualsDefaults: Partial<Record<ParameterSettingType, (a: unknown, b: unknown) => boolean>> = {
|
|
187
187
|
'boolean': (a: unknown, b: unknown) => Boolean(a) === Boolean(b),
|
|
188
188
|
'directory': (a: unknown, b: unknown) => path.resolve(untildify(String(a))) === path.resolve(untildify(String(b))),
|
|
189
189
|
'number': (a: unknown, b: unknown) => Number(a) === Number(b),
|
|
190
190
|
'string': (a: unknown, b: unknown) => String(a) === String(b),
|
|
191
191
|
'version': (desired: unknown, current: unknown) => String(current).includes(String(desired))
|
|
192
192
|
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
export function resolveEqualsFn(parameter: ParameterSetting, key: string): (desired: unknown, current: unknown) => boolean {
|
|
196
|
+
if (parameter.type === 'array') {
|
|
197
|
+
return parameter.isEqual ?? areArraysEqual.bind(areArraysEqual, parameter as ArrayParameterSetting)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (parameter.type === 'stateful') {
|
|
201
|
+
return resolveEqualsFn((parameter as StatefulParameterSetting).definition.getSettings(), key)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return parameter.isEqual ?? ParameterEqualsDefaults[parameter.type as ParameterSettingType] ?? (((a, b) => a === b));
|
|
205
|
+
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { spy } from 'sinon';
|
|
3
3
|
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
TestArrayStatefulParameter,
|
|
6
|
+
TestConfig,
|
|
7
|
+
testPlan,
|
|
8
|
+
TestResource,
|
|
9
|
+
TestStatefulParameter
|
|
10
|
+
} from '../utils/test-utils.test.js';
|
|
11
|
+
import { ArrayParameterSetting, ParameterSetting, ResourceSettings } from './resource-settings.js';
|
|
12
|
+
import { ResourceController } from './resource-controller.js';
|
|
6
13
|
|
|
7
14
|
describe('Stateful parameter tests', () => {
|
|
8
15
|
it('addItem is called the correct number of times', async () => {
|
|
@@ -49,7 +56,7 @@ describe('Stateful parameter tests', () => {
|
|
|
49
56
|
expect(plan.changeSet.operation).to.eq(ResourceOperation.MODIFY);
|
|
50
57
|
expect(plan.changeSet.parameterChanges[0]).toMatchObject({
|
|
51
58
|
name: 'propZ',
|
|
52
|
-
previousValue: ['
|
|
59
|
+
previousValue: ['c', 'a'], // In stateless mode the previous value gets filtered to prevent deletes
|
|
53
60
|
newValue: ['a', 'c', 'd', 'e', 'f'],
|
|
54
61
|
operation: ParameterOperation.MODIFY,
|
|
55
62
|
})
|
|
@@ -108,4 +115,44 @@ describe('Stateful parameter tests', () => {
|
|
|
108
115
|
|
|
109
116
|
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
110
117
|
})
|
|
118
|
+
|
|
119
|
+
it('isElementEquals test', async () => {
|
|
120
|
+
const testParameter = spy(new class extends TestArrayStatefulParameter {
|
|
121
|
+
getSettings(): ArrayParameterSetting {
|
|
122
|
+
return {
|
|
123
|
+
type: 'array',
|
|
124
|
+
isElementEqual: (desired, current) => current.includes(desired),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async refresh(): Promise<any> {
|
|
129
|
+
return [
|
|
130
|
+
'20.15.0',
|
|
131
|
+
'20.15.1'
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const resource = new class extends TestResource {
|
|
137
|
+
getSettings(): ResourceSettings<any> {
|
|
138
|
+
return {
|
|
139
|
+
id: 'type',
|
|
140
|
+
parameterSettings: { nodeVersions: { type: 'stateful', definition: testParameter } }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async refresh(): Promise<Partial<any> | null> {
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const controller = new ResourceController(resource);
|
|
150
|
+
const plan = await controller.plan({
|
|
151
|
+
nodeVersions: ['20.15'],
|
|
152
|
+
} as any)
|
|
153
|
+
|
|
154
|
+
console.log(JSON.stringify(plan, null, 2))
|
|
155
|
+
|
|
156
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
157
|
+
})
|
|
111
158
|
})
|
|
@@ -31,10 +31,11 @@ export abstract class StatefulParameter<T extends StringIndexedObject, V extends
|
|
|
31
31
|
* Return the value of the stateful parameter or null if not found.
|
|
32
32
|
*
|
|
33
33
|
* @param desired The desired value of the user.
|
|
34
|
+
* @param config The desired config
|
|
34
35
|
*
|
|
35
36
|
* @return The value of the stateful parameter currently on the system or null if not found
|
|
36
37
|
*/
|
|
37
|
-
abstract refresh(desired: V | null): Promise<V | null>;
|
|
38
|
+
abstract refresh(desired: V | null, config: Partial<T>): Promise<V | null>;
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* Create the stateful parameter on the system. This method is similar {@link Resource.create} except that its only
|