codify-plugin-lib 1.0.75 → 1.0.77
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/.eslintrc.json +11 -4
- package/.github/workflows/release.yaml +19 -0
- package/.github/workflows/unit-test-ci.yaml +19 -0
- package/dist/entities/plugin.d.ts +1 -1
- package/dist/entities/plugin.js +5 -5
- package/dist/entities/resource-options.d.ts +6 -6
- package/dist/entities/resource-options.js +7 -9
- package/dist/entities/resource.d.ts +2 -3
- package/dist/entities/resource.js +2 -2
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +7 -0
- package/dist/index.d.ts +10 -10
- package/dist/index.js +9 -9
- package/dist/messages/handlers.d.ts +1 -1
- package/dist/messages/handlers.js +25 -24
- package/dist/plan/change-set.d.ts +37 -0
- package/dist/plan/change-set.js +146 -0
- package/dist/plan/plan-types.d.ts +23 -0
- package/dist/plan/plan-types.js +1 -0
- package/dist/plan/plan.d.ts +59 -0
- package/dist/plan/plan.js +228 -0
- package/dist/plugin/plugin.d.ts +17 -0
- package/dist/plugin/plugin.js +83 -0
- package/dist/resource/config-parser.d.ts +14 -0
- package/dist/resource/config-parser.js +48 -0
- package/dist/resource/parsed-resource-settings.d.ts +26 -0
- package/dist/resource/parsed-resource-settings.js +126 -0
- package/dist/resource/resource-controller.d.ts +30 -0
- package/dist/resource/resource-controller.js +247 -0
- package/dist/resource/resource-settings.d.ts +149 -0
- package/dist/resource/resource-settings.js +9 -0
- package/dist/resource/resource.d.ts +137 -0
- package/dist/resource/resource.js +44 -0
- package/dist/resource/stateful-parameter.d.ts +164 -0
- package/dist/resource/stateful-parameter.js +94 -0
- package/dist/utils/utils.d.ts +19 -3
- package/dist/utils/utils.js +52 -3
- package/package.json +6 -4
- package/src/index.ts +10 -11
- package/src/messages/handlers.test.ts +21 -42
- package/src/messages/handlers.ts +28 -27
- package/src/plan/change-set.test.ts +220 -0
- package/src/plan/change-set.ts +225 -0
- package/src/plan/plan-types.ts +27 -0
- package/src/{entities → plan}/plan.test.ts +35 -29
- package/src/plan/plan.ts +353 -0
- package/src/{entities → plugin}/plugin.test.ts +14 -13
- package/src/{entities → plugin}/plugin.ts +32 -27
- package/src/resource/config-parser.ts +77 -0
- package/src/{entities/resource-options.test.ts → resource/parsed-resource-settings.test.ts} +8 -7
- package/src/resource/parsed-resource-settings.ts +179 -0
- package/src/{entities/resource-stateful-mode.test.ts → resource/resource-controller-stateful-mode.test.ts} +36 -39
- package/src/{entities/resource.test.ts → resource/resource-controller.test.ts} +116 -176
- package/src/resource/resource-controller.ts +340 -0
- package/src/resource/resource-settings.test.ts +494 -0
- package/src/resource/resource-settings.ts +192 -0
- package/src/resource/resource.ts +149 -0
- package/src/resource/stateful-parameter.test.ts +93 -0
- package/src/resource/stateful-parameter.ts +217 -0
- package/src/utils/test-utils.test.ts +87 -0
- package/src/utils/utils.test.ts +2 -2
- package/src/utils/utils.ts +51 -5
- package/tsconfig.json +0 -1
- package/vitest.config.ts +10 -0
- package/src/entities/change-set.test.ts +0 -155
- package/src/entities/change-set.ts +0 -244
- package/src/entities/plan-types.ts +0 -44
- package/src/entities/plan.ts +0 -178
- package/src/entities/resource-options.ts +0 -156
- package/src/entities/resource-parameters.test.ts +0 -604
- package/src/entities/resource-types.ts +0 -31
- package/src/entities/resource.ts +0 -471
- package/src/entities/stateful-parameter.test.ts +0 -114
- package/src/entities/stateful-parameter.ts +0 -92
- package/src/entities/transform-parameter.ts +0 -13
- /package/src/{entities/errors.ts → errors.ts} +0 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { ChangeSet } from './change-set.js';
|
|
2
|
+
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
describe('Change set tests', () => {
|
|
6
|
+
it ('Correctly diffs two resource configs (modify)', () => {
|
|
7
|
+
const after = {
|
|
8
|
+
propA: 'before',
|
|
9
|
+
propB: 'before'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const before = {
|
|
13
|
+
propA: 'after',
|
|
14
|
+
propB: 'after'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const cs = ChangeSet.calculateModification(after, before);
|
|
18
|
+
expect(cs.parameterChanges.length).to.eq(2);
|
|
19
|
+
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
20
|
+
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.MODIFY);
|
|
21
|
+
expect(cs.operation).to.eq(ResourceOperation.RECREATE)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it ('Correctly diffs two resource configs (add)', () => {
|
|
25
|
+
const after = {
|
|
26
|
+
propA: 'before',
|
|
27
|
+
propB: 'after'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const before = {
|
|
31
|
+
propA: 'after',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cs = ChangeSet.calculateModification(after, before,);
|
|
35
|
+
expect(cs.parameterChanges.length).to.eq(2);
|
|
36
|
+
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
37
|
+
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.ADD);
|
|
38
|
+
expect(cs.operation).to.eq(ResourceOperation.RECREATE)
|
|
39
|
+
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it ('Correctly diffs two resource configs (remove)', () => {
|
|
43
|
+
const after = {
|
|
44
|
+
propA: 'after',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const before = {
|
|
48
|
+
propA: 'before',
|
|
49
|
+
propB: 'before'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const cs = ChangeSet.calculateModification(after, before);
|
|
53
|
+
expect(cs.parameterChanges.length).to.eq(2);
|
|
54
|
+
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
55
|
+
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.REMOVE);
|
|
56
|
+
expect(cs.operation).to.eq(ResourceOperation.RECREATE)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it ('Correctly diffs two resource configs (no-op)', () => {
|
|
60
|
+
const after = {
|
|
61
|
+
propA: 'prop',
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const before = {
|
|
65
|
+
propA: 'prop',
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const cs = ChangeSet.calculateModification(after, before);
|
|
69
|
+
expect(cs.parameterChanges.length).to.eq(1);
|
|
70
|
+
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
|
|
71
|
+
expect(cs.operation).to.eq(ResourceOperation.NOOP)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('Correctly diffs two resource configs (create)', () => {
|
|
75
|
+
const cs = ChangeSet.create({
|
|
76
|
+
propA: 'prop',
|
|
77
|
+
propB: 'propB'
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(cs.parameterChanges.length).to.eq(2);
|
|
81
|
+
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.ADD);
|
|
82
|
+
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.ADD);
|
|
83
|
+
expect(cs.operation).to.eq(ResourceOperation.CREATE)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('Correctly diffs two resource configs (destory)', () => {
|
|
87
|
+
const cs = ChangeSet.destroy({
|
|
88
|
+
propA: 'prop',
|
|
89
|
+
propB: 'propB'
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(cs.parameterChanges.length).to.eq(2);
|
|
93
|
+
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.REMOVE);
|
|
94
|
+
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.REMOVE);
|
|
95
|
+
expect(cs.operation).to.eq(ResourceOperation.DESTROY)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it ('handles simple arrays', () => {
|
|
99
|
+
const before = {
|
|
100
|
+
propA: ['a', 'b', 'c'],
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const after = {
|
|
104
|
+
propA: ['b', 'a', 'c'],
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const cs = ChangeSet.calculateModification(after, before, { propA: { type: 'array' } });
|
|
108
|
+
expect(cs.parameterChanges.length).to.eq(1);
|
|
109
|
+
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
|
|
110
|
+
expect(cs.operation).to.eq(ResourceOperation.NOOP)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('handles simple arrays 2', () => {
|
|
114
|
+
const after = {
|
|
115
|
+
propA: ['a', 'b', 'c'],
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const before = {
|
|
119
|
+
propA: ['b', 'a'],
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const cs = ChangeSet.calculateModification(after, before, { propA: { type: 'array' } });
|
|
123
|
+
expect(cs.parameterChanges.length).to.eq(1);
|
|
124
|
+
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
125
|
+
expect(cs.operation).to.eq(ResourceOperation.RECREATE)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('determines the order of operations with canModify 1', () => {
|
|
129
|
+
const after = {
|
|
130
|
+
propA: 'after',
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const before = {
|
|
134
|
+
propA: 'before',
|
|
135
|
+
propB: 'before'
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const cs = ChangeSet.calculateModification(after, before, { propA: { canModify: true } });
|
|
139
|
+
expect(cs.parameterChanges.length).to.eq(2);
|
|
140
|
+
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
141
|
+
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.REMOVE);
|
|
142
|
+
expect(cs.operation).to.eq(ResourceOperation.RECREATE)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('determines the order of operations with canModify 2', () => {
|
|
146
|
+
const after = {
|
|
147
|
+
propA: 'after',
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const before = {
|
|
151
|
+
propA: 'before',
|
|
152
|
+
propB: 'before'
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const cs = ChangeSet.calculateModification<any>(after, before, {
|
|
156
|
+
propA: { canModify: true },
|
|
157
|
+
propB: { canModify: true }
|
|
158
|
+
});
|
|
159
|
+
expect(cs.parameterChanges.length).to.eq(2);
|
|
160
|
+
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
161
|
+
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.REMOVE);
|
|
162
|
+
expect(cs.operation).to.eq(ResourceOperation.MODIFY)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
it('correctly determines array equality', () => {
|
|
167
|
+
const arrA = ['a', 'b', 'd'];
|
|
168
|
+
const arrB = ['a', 'b', 'd'];
|
|
169
|
+
|
|
170
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, { propA: { type: 'array' } })
|
|
171
|
+
|
|
172
|
+
expect(result.operation).to.eq(ResourceOperation.NOOP);
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('correctly determines array equality 2', () => {
|
|
176
|
+
const arrA = ['a', 'b'];
|
|
177
|
+
const arrB = ['a', 'b', 'd'];
|
|
178
|
+
|
|
179
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, { propA: { type: 'array' } })
|
|
180
|
+
|
|
181
|
+
expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('correctly determines array equality 3', () => {
|
|
185
|
+
const arrA = ['b', 'a', 'd'];
|
|
186
|
+
const arrB = ['a', 'b', 'd'];
|
|
187
|
+
|
|
188
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, { propA: { type: 'array' } })
|
|
189
|
+
|
|
190
|
+
expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('correctly determines array equality 4', () => {
|
|
194
|
+
const arrA = [{ key1: 'a' }, { key1: 'a' }, { key1: 'a' }];
|
|
195
|
+
const arrB = [{ key1: 'a' }, { key1: 'a' }, { key1: 'b' }];
|
|
196
|
+
|
|
197
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, {
|
|
198
|
+
propA: {
|
|
199
|
+
type: 'array',
|
|
200
|
+
isElementEqual: (a, b) => a.key1 === b.key1
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('correctly determines array equality 5', () => {
|
|
208
|
+
const arrA = [{ key1: 'b' }, { key1: 'a' }, { key1: 'a' }];
|
|
209
|
+
const arrB = [{ key1: 'a' }, { key1: 'a' }, { key1: 'b' }];
|
|
210
|
+
|
|
211
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, {
|
|
212
|
+
propA: {
|
|
213
|
+
type: 'array',
|
|
214
|
+
isElementEqual: (a, b) => a.key1 === b.key1
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
|
|
219
|
+
})
|
|
220
|
+
})
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
|
|
2
|
+
|
|
3
|
+
import { ArrayParameterSetting, ParameterSetting, StatefulParameterSetting } from '../resource/resource-settings.js';
|
|
4
|
+
import { areArraysEqual } from '../utils/utils.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
|
+
// Change set will coerce undefined values to null because undefined is not valid JSON
|
|
32
|
+
export class ChangeSet<T extends StringIndexedObject> {
|
|
33
|
+
operation: ResourceOperation
|
|
34
|
+
parameterChanges: Array<ParameterChange<T>>
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
operation: ResourceOperation,
|
|
38
|
+
parameterChanges: Array<ParameterChange<T>>
|
|
39
|
+
) {
|
|
40
|
+
this.operation = operation;
|
|
41
|
+
this.parameterChanges = parameterChanges;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get desiredParameters(): T {
|
|
45
|
+
return this.parameterChanges
|
|
46
|
+
.reduce((obj, pc) => ({
|
|
47
|
+
...obj,
|
|
48
|
+
[pc.name]: pc.newValue,
|
|
49
|
+
}), {}) as T;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get currentParameters(): T {
|
|
53
|
+
return this.parameterChanges
|
|
54
|
+
.reduce((obj, pc) => ({
|
|
55
|
+
...obj,
|
|
56
|
+
[pc.name]: pc.previousValue,
|
|
57
|
+
}), {}) as T;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static empty<T extends StringIndexedObject>(): ChangeSet<T> {
|
|
61
|
+
return new ChangeSet<T>(ResourceOperation.NOOP, []);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static create<T extends StringIndexedObject>(desired: Partial<T>): ChangeSet<T> {
|
|
65
|
+
const parameterChanges = Object.entries(desired)
|
|
66
|
+
.map(([k, v]) => ({
|
|
67
|
+
name: k,
|
|
68
|
+
operation: ParameterOperation.ADD,
|
|
69
|
+
previousValue: null,
|
|
70
|
+
newValue: v ?? null,
|
|
71
|
+
}))
|
|
72
|
+
|
|
73
|
+
return new ChangeSet(ResourceOperation.CREATE, parameterChanges);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static destroy<T extends StringIndexedObject>(current: Partial<T>): ChangeSet<T> {
|
|
77
|
+
const parameterChanges = Object.entries(current)
|
|
78
|
+
.map(([k, v]) => ({
|
|
79
|
+
name: k,
|
|
80
|
+
operation: ParameterOperation.REMOVE,
|
|
81
|
+
previousValue: v ?? null,
|
|
82
|
+
newValue: null,
|
|
83
|
+
}))
|
|
84
|
+
|
|
85
|
+
return new ChangeSet(ResourceOperation.DESTROY, parameterChanges);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static calculateModification<T extends StringIndexedObject>(
|
|
89
|
+
desired: Partial<T>,
|
|
90
|
+
current: Partial<T>,
|
|
91
|
+
parameterSettings: Partial<Record<keyof T, ParameterSetting>> = {},
|
|
92
|
+
): ChangeSet<T> {
|
|
93
|
+
const pc = ChangeSet.calculateParameterChanges(desired, current, parameterSettings);
|
|
94
|
+
|
|
95
|
+
const statefulParameterKeys = new Set(
|
|
96
|
+
Object.entries(parameterSettings)
|
|
97
|
+
.filter(([, v]) => v?.type === 'stateful')
|
|
98
|
+
.map(([k]) => k)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
const resourceOperation = pc
|
|
102
|
+
.filter((change) => change.operation !== ParameterOperation.NOOP)
|
|
103
|
+
.reduce((operation: ResourceOperation, curr: ParameterChange<T>) => {
|
|
104
|
+
let newOperation: ResourceOperation;
|
|
105
|
+
if (statefulParameterKeys.has(curr.name)) {
|
|
106
|
+
newOperation = ResourceOperation.MODIFY // All stateful parameters are modify only
|
|
107
|
+
} else if (parameterSettings[curr.name]?.canModify) {
|
|
108
|
+
newOperation = ResourceOperation.MODIFY
|
|
109
|
+
} else {
|
|
110
|
+
newOperation = ResourceOperation.RECREATE; // Default to Re-create. Should handle the majority of use cases
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return ChangeSet.combineResourceOperations(operation, newOperation);
|
|
114
|
+
}, ResourceOperation.NOOP);
|
|
115
|
+
|
|
116
|
+
return new ChangeSet<T>(resourceOperation, pc);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private static calculateParameterChanges<T extends StringIndexedObject>(
|
|
120
|
+
desiredParameters: Partial<T>,
|
|
121
|
+
currentParameters: Partial<T>,
|
|
122
|
+
parameterOptions?: Partial<Record<keyof T, ParameterSetting>>,
|
|
123
|
+
): ParameterChange<T>[] {
|
|
124
|
+
const parameterChangeSet = new Array<ParameterChange<T>>();
|
|
125
|
+
|
|
126
|
+
// Filter out null and undefined values or else the diff below will not work
|
|
127
|
+
const desired = Object.fromEntries(
|
|
128
|
+
Object.entries(desiredParameters).filter(([, v]) => v !== null && v !== undefined)
|
|
129
|
+
) as Partial<T>
|
|
130
|
+
|
|
131
|
+
const current = Object.fromEntries(
|
|
132
|
+
Object.entries(currentParameters).filter(([, v]) => v !== null && v !== undefined)
|
|
133
|
+
) as Partial<T>
|
|
134
|
+
|
|
135
|
+
for (const [k, v] of Object.entries(current)) {
|
|
136
|
+
if (desired?.[k] === null || desired?.[k] === undefined) {
|
|
137
|
+
parameterChangeSet.push({
|
|
138
|
+
name: k,
|
|
139
|
+
previousValue: v ?? null,
|
|
140
|
+
newValue: null,
|
|
141
|
+
operation: ParameterOperation.REMOVE,
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
delete current[k];
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!ChangeSet.isSame(desired[k], current[k], parameterOptions?.[k])) {
|
|
149
|
+
parameterChangeSet.push({
|
|
150
|
+
name: k,
|
|
151
|
+
previousValue: v ?? null,
|
|
152
|
+
newValue: desired[k] ?? null,
|
|
153
|
+
operation: ParameterOperation.MODIFY,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
delete current[k];
|
|
157
|
+
delete desired[k];
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
parameterChangeSet.push({
|
|
162
|
+
name: k,
|
|
163
|
+
previousValue: v ?? null,
|
|
164
|
+
newValue: desired[k] ?? null,
|
|
165
|
+
operation: ParameterOperation.NOOP,
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
delete current[k];
|
|
169
|
+
delete desired[k];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (Object.keys(current).length > 0) {
|
|
173
|
+
throw new Error('Diff algorithm error');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
for (const [k, v] of Object.entries(desired)) {
|
|
177
|
+
parameterChangeSet.push({
|
|
178
|
+
name: k,
|
|
179
|
+
previousValue: null,
|
|
180
|
+
newValue: v ?? null,
|
|
181
|
+
operation: ParameterOperation.ADD,
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return parameterChangeSet;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private static combineResourceOperations(prev: ResourceOperation, next: ResourceOperation) {
|
|
189
|
+
const orderOfOperations = [
|
|
190
|
+
ResourceOperation.NOOP,
|
|
191
|
+
ResourceOperation.MODIFY,
|
|
192
|
+
ResourceOperation.RECREATE,
|
|
193
|
+
ResourceOperation.CREATE,
|
|
194
|
+
ResourceOperation.DESTROY,
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
const indexPrev = orderOfOperations.indexOf(prev);
|
|
198
|
+
const indexNext = orderOfOperations.indexOf(next);
|
|
199
|
+
|
|
200
|
+
return orderOfOperations[Math.max(indexPrev, indexNext)];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private static isSame(
|
|
204
|
+
desired: unknown,
|
|
205
|
+
current: unknown,
|
|
206
|
+
setting?: ParameterSetting,
|
|
207
|
+
): boolean {
|
|
208
|
+
switch (setting?.type) {
|
|
209
|
+
case 'stateful': {
|
|
210
|
+
const statefulSetting = (setting as StatefulParameterSetting).definition.getSettings()
|
|
211
|
+
|
|
212
|
+
return ChangeSet.isSame(desired, current, statefulSetting as ParameterSetting);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
case 'array': {
|
|
216
|
+
const arrayParameter = setting as ArrayParameterSetting;
|
|
217
|
+
return areArraysEqual(arrayParameter, desired, current)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
default: {
|
|
221
|
+
return (setting?.isEqual ?? ((a, b) => a === b))(desired, current)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { Plan } from './plan.js';
|
|
3
|
-
import { TestResource } from './resource.test.js';
|
|
4
3
|
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
5
|
-
import {
|
|
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';
|
|
6
8
|
|
|
7
9
|
describe('Plan entity tests', () => {
|
|
8
10
|
it('Adds default values properly when plan is parsed from request (Create)', () => {
|
|
9
|
-
const resource =
|
|
11
|
+
const resource = createTestResource()
|
|
12
|
+
const controller = new ResourceController(resource);
|
|
10
13
|
|
|
11
14
|
const plan = Plan.fromResponse({
|
|
12
15
|
operation: ResourceOperation.CREATE,
|
|
@@ -17,7 +20,7 @@ describe('Plan entity tests', () => {
|
|
|
17
20
|
previousValue: null,
|
|
18
21
|
newValue: 'propBValue'
|
|
19
22
|
}]
|
|
20
|
-
},
|
|
23
|
+
}, controller.parsedSettings.defaultValues);
|
|
21
24
|
|
|
22
25
|
expect(plan.currentConfig).to.be.null;
|
|
23
26
|
|
|
@@ -33,7 +36,8 @@ describe('Plan entity tests', () => {
|
|
|
33
36
|
})
|
|
34
37
|
|
|
35
38
|
it('Adds default values properly when plan is parsed from request (Destroy)', () => {
|
|
36
|
-
const resource =
|
|
39
|
+
const resource = createTestResource()
|
|
40
|
+
const controller = new ResourceController(resource);
|
|
37
41
|
|
|
38
42
|
const plan = Plan.fromResponse({
|
|
39
43
|
operation: ResourceOperation.DESTROY,
|
|
@@ -44,7 +48,7 @@ describe('Plan entity tests', () => {
|
|
|
44
48
|
previousValue: 'propBValue',
|
|
45
49
|
newValue: null,
|
|
46
50
|
}]
|
|
47
|
-
},
|
|
51
|
+
}, controller.parsedSettings.defaultValues);
|
|
48
52
|
|
|
49
53
|
expect(plan.currentConfig).toMatchObject({
|
|
50
54
|
type: 'type',
|
|
@@ -60,7 +64,8 @@ describe('Plan entity tests', () => {
|
|
|
60
64
|
})
|
|
61
65
|
|
|
62
66
|
it('Adds default values properly when plan is parsed from request (No-op)', () => {
|
|
63
|
-
const resource =
|
|
67
|
+
const resource = createTestResource()
|
|
68
|
+
const controller = new ResourceController(resource);
|
|
64
69
|
|
|
65
70
|
const plan = Plan.fromResponse({
|
|
66
71
|
operation: ResourceOperation.NOOP,
|
|
@@ -71,7 +76,7 @@ describe('Plan entity tests', () => {
|
|
|
71
76
|
previousValue: 'propBValue',
|
|
72
77
|
newValue: 'propBValue',
|
|
73
78
|
}]
|
|
74
|
-
},
|
|
79
|
+
}, controller.parsedSettings.defaultValues);
|
|
75
80
|
|
|
76
81
|
expect(plan.currentConfig).toMatchObject({
|
|
77
82
|
type: 'type',
|
|
@@ -91,7 +96,8 @@ describe('Plan entity tests', () => {
|
|
|
91
96
|
})
|
|
92
97
|
|
|
93
98
|
it('Does not add default value if a value has already been specified', () => {
|
|
94
|
-
const resource =
|
|
99
|
+
const resource = createTestResource()
|
|
100
|
+
const controller = new ResourceController(resource);
|
|
95
101
|
|
|
96
102
|
const plan = Plan.fromResponse({
|
|
97
103
|
operation: ResourceOperation.CREATE,
|
|
@@ -107,7 +113,7 @@ describe('Plan entity tests', () => {
|
|
|
107
113
|
previousValue: null,
|
|
108
114
|
newValue: 'propAValue',
|
|
109
115
|
}]
|
|
110
|
-
},
|
|
116
|
+
}, controller.parsedSettings.defaultValues);
|
|
111
117
|
|
|
112
118
|
expect(plan.currentConfig).to.be.null
|
|
113
119
|
|
|
@@ -123,19 +129,17 @@ describe('Plan entity tests', () => {
|
|
|
123
129
|
})
|
|
124
130
|
|
|
125
131
|
it('Returns the original resource names', () => {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
propA: 'propA2',
|
|
134
|
-
},
|
|
135
|
-
{
|
|
132
|
+
const plan = Plan.calculate<TestConfig>({
|
|
133
|
+
desiredParameters: { propA: 'propA' },
|
|
134
|
+
currentParametersArray: [{ propA: 'propA2' }],
|
|
135
|
+
stateParameters: null,
|
|
136
|
+
coreParameters: {
|
|
136
137
|
type: 'type',
|
|
137
138
|
name: 'name1'
|
|
138
|
-
},
|
|
139
|
+
},
|
|
140
|
+
settings: new ParsedResourceSettings<TestConfig>({ id: 'type' }),
|
|
141
|
+
statefulMode: false,
|
|
142
|
+
});
|
|
139
143
|
|
|
140
144
|
expect(plan.toResponse()).toMatchObject({
|
|
141
145
|
resourceType: 'type',
|
|
@@ -145,15 +149,17 @@ describe('Plan entity tests', () => {
|
|
|
145
149
|
})
|
|
146
150
|
})
|
|
147
151
|
|
|
148
|
-
function
|
|
152
|
+
function createTestResource() {
|
|
149
153
|
return new class extends TestResource {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
propA: {
|
|
154
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
155
|
+
return {
|
|
156
|
+
id: 'type',
|
|
157
|
+
parameterSettings: {
|
|
158
|
+
propA: {
|
|
159
|
+
default: 'defaultA'
|
|
160
|
+
}
|
|
155
161
|
}
|
|
156
|
-
}
|
|
162
|
+
}
|
|
157
163
|
}
|
|
158
|
-
}
|
|
164
|
+
};
|
|
159
165
|
}
|