codify-plugin-lib 1.0.115 → 1.0.116
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/index.js +3 -0
- package/dist/plan/plan.d.ts +10 -10
- package/dist/plan/plan.js +84 -84
- package/dist/plugin/plugin.d.ts +1 -0
- package/dist/plugin/plugin.js +9 -1
- package/package.json +1 -1
- package/src/common/errors.test.ts +1 -2
- package/src/common/errors.ts +1 -1
- package/src/index.ts +4 -0
- package/src/plan/plan.ts +123 -122
- package/src/plugin/plugin.test.ts +1 -2
- package/src/plugin/plugin.ts +17 -8
package/dist/index.js
CHANGED
|
@@ -13,4 +13,7 @@ export * from './utils/utils.js';
|
|
|
13
13
|
export async function runPlugin(plugin) {
|
|
14
14
|
const messageHandler = new MessageHandler(plugin);
|
|
15
15
|
process.on('message', (message) => messageHandler.onMessage(message));
|
|
16
|
+
process.on('beforeExit', () => {
|
|
17
|
+
plugin.kill();
|
|
18
|
+
});
|
|
16
19
|
}
|
package/dist/plan/plan.d.ts
CHANGED
|
@@ -27,7 +27,14 @@ export declare class Plan<T extends StringIndexedObject> {
|
|
|
27
27
|
*/
|
|
28
28
|
get currentConfig(): T | null;
|
|
29
29
|
get resourceId(): string;
|
|
30
|
-
|
|
30
|
+
static calculate<T extends StringIndexedObject>(params: {
|
|
31
|
+
desiredParameters: Partial<T> | null;
|
|
32
|
+
currentParametersArray: Partial<T>[] | null;
|
|
33
|
+
stateParameters: Partial<T> | null;
|
|
34
|
+
coreParameters: ResourceConfig;
|
|
35
|
+
settings: ParsedResourceSettings<T>;
|
|
36
|
+
statefulMode: boolean;
|
|
37
|
+
}): Plan<T>;
|
|
31
38
|
/**
|
|
32
39
|
* When multiples of the same resource are allowed, this matching function will match a given config with one of the
|
|
33
40
|
* existing configs on the system. For example if there are multiple versions of Android Studios installed, we can use
|
|
@@ -43,14 +50,7 @@ export declare class Plan<T extends StringIndexedObject> {
|
|
|
43
50
|
* @return string
|
|
44
51
|
*/
|
|
45
52
|
getResourceType(): string;
|
|
46
|
-
static
|
|
47
|
-
desiredParameters: Partial<T> | null;
|
|
48
|
-
currentParametersArray: Partial<T>[] | null;
|
|
49
|
-
stateParameters: Partial<T> | null;
|
|
50
|
-
coreParameters: ResourceConfig;
|
|
51
|
-
settings: ParsedResourceSettings<T>;
|
|
52
|
-
statefulMode: boolean;
|
|
53
|
-
}): Plan<T>;
|
|
53
|
+
static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues?: Partial<Record<keyof T, unknown>>): Plan<T>;
|
|
54
54
|
/**
|
|
55
55
|
* Only keep relevant params for the plan. We don't want to change settings that were not already
|
|
56
56
|
* defined.
|
|
@@ -60,7 +60,7 @@ export declare class Plan<T extends StringIndexedObject> {
|
|
|
60
60
|
* or wants to set. If a parameter is not specified then it's not managed by Codify.
|
|
61
61
|
*/
|
|
62
62
|
private static filterCurrentParams;
|
|
63
|
-
|
|
63
|
+
requiresChanges(): boolean;
|
|
64
64
|
/**
|
|
65
65
|
* Convert the plan to a JSON response object
|
|
66
66
|
*/
|
package/dist/plan/plan.js
CHANGED
|
@@ -52,8 +52,37 @@ export class Plan {
|
|
|
52
52
|
? `${this.coreParameters.type}.${this.coreParameters.name}`
|
|
53
53
|
: this.coreParameters.type;
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
static calculate(params) {
|
|
56
|
+
const { desiredParameters, currentParametersArray, stateParameters, coreParameters, settings, statefulMode } = params;
|
|
57
|
+
const currentParameters = Plan.matchCurrentParameters({
|
|
58
|
+
desiredParameters,
|
|
59
|
+
currentParametersArray,
|
|
60
|
+
stateParameters,
|
|
61
|
+
settings,
|
|
62
|
+
statefulMode
|
|
63
|
+
});
|
|
64
|
+
const filteredCurrentParameters = Plan.filterCurrentParams({
|
|
65
|
+
desiredParameters,
|
|
66
|
+
currentParameters,
|
|
67
|
+
stateParameters,
|
|
68
|
+
settings,
|
|
69
|
+
statefulMode
|
|
70
|
+
});
|
|
71
|
+
// Empty
|
|
72
|
+
if (!filteredCurrentParameters && !desiredParameters) {
|
|
73
|
+
return new Plan(uuidV4(), ChangeSet.empty(), coreParameters, statefulMode);
|
|
74
|
+
}
|
|
75
|
+
// CREATE
|
|
76
|
+
if (!filteredCurrentParameters && desiredParameters) {
|
|
77
|
+
return new Plan(uuidV4(), ChangeSet.create(desiredParameters), coreParameters, statefulMode);
|
|
78
|
+
}
|
|
79
|
+
// DESTROY
|
|
80
|
+
if (filteredCurrentParameters && !desiredParameters) {
|
|
81
|
+
return new Plan(uuidV4(), ChangeSet.destroy(filteredCurrentParameters), coreParameters, statefulMode);
|
|
82
|
+
}
|
|
83
|
+
// NO-OP, MODIFY or RE-CREATE
|
|
84
|
+
const changeSet = ChangeSet.calculateModification(desiredParameters, filteredCurrentParameters, settings.parameterSettings);
|
|
85
|
+
return new Plan(uuidV4(), changeSet, coreParameters, statefulMode);
|
|
57
86
|
}
|
|
58
87
|
/**
|
|
59
88
|
* When multiples of the same resource are allowed, this matching function will match a given config with one of the
|
|
@@ -86,37 +115,59 @@ export class Plan {
|
|
|
86
115
|
getResourceType() {
|
|
87
116
|
return this.coreParameters.type;
|
|
88
117
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
currentParametersArray,
|
|
94
|
-
stateParameters,
|
|
95
|
-
settings,
|
|
96
|
-
statefulMode
|
|
97
|
-
});
|
|
98
|
-
const filteredCurrentParameters = Plan.filterCurrentParams({
|
|
99
|
-
desiredParameters,
|
|
100
|
-
currentParameters,
|
|
101
|
-
stateParameters,
|
|
102
|
-
settings,
|
|
103
|
-
statefulMode
|
|
104
|
-
});
|
|
105
|
-
// Empty
|
|
106
|
-
if (!filteredCurrentParameters && !desiredParameters) {
|
|
107
|
-
return new Plan(uuidV4(), ChangeSet.empty(), coreParameters, statefulMode);
|
|
108
|
-
}
|
|
109
|
-
// CREATE
|
|
110
|
-
if (!filteredCurrentParameters && desiredParameters) {
|
|
111
|
-
return new Plan(uuidV4(), ChangeSet.create(desiredParameters), coreParameters, statefulMode);
|
|
118
|
+
// 2. Even if there was (maybe for testing reasons), the plan values should not be adjusted
|
|
119
|
+
static fromResponse(data, defaultValues) {
|
|
120
|
+
if (!data) {
|
|
121
|
+
throw new Error('Data is empty');
|
|
112
122
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
123
|
+
addDefaultValues();
|
|
124
|
+
return new Plan(uuidV4(), new ChangeSet(data.operation, data.parameters), {
|
|
125
|
+
type: data.resourceType,
|
|
126
|
+
name: data.resourceName,
|
|
127
|
+
}, data.statefulMode);
|
|
128
|
+
function addDefaultValues() {
|
|
129
|
+
Object.entries(defaultValues ?? {})
|
|
130
|
+
.forEach(([key, defaultValue]) => {
|
|
131
|
+
const configValueExists = data
|
|
132
|
+
.parameters
|
|
133
|
+
.some((p) => p.name === key);
|
|
134
|
+
// Only set default values if the value does not exist in the config
|
|
135
|
+
if (configValueExists) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
switch (data.operation) {
|
|
139
|
+
case ResourceOperation.CREATE: {
|
|
140
|
+
data.parameters.push({
|
|
141
|
+
name: key,
|
|
142
|
+
operation: ParameterOperation.ADD,
|
|
143
|
+
previousValue: null,
|
|
144
|
+
newValue: defaultValue,
|
|
145
|
+
});
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case ResourceOperation.DESTROY: {
|
|
149
|
+
data.parameters.push({
|
|
150
|
+
name: key,
|
|
151
|
+
operation: ParameterOperation.REMOVE,
|
|
152
|
+
previousValue: defaultValue,
|
|
153
|
+
newValue: null,
|
|
154
|
+
});
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case ResourceOperation.MODIFY:
|
|
158
|
+
case ResourceOperation.RECREATE:
|
|
159
|
+
case ResourceOperation.NOOP: {
|
|
160
|
+
data.parameters.push({
|
|
161
|
+
name: key,
|
|
162
|
+
operation: ParameterOperation.NOOP,
|
|
163
|
+
previousValue: defaultValue,
|
|
164
|
+
newValue: defaultValue,
|
|
165
|
+
});
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
116
170
|
}
|
|
117
|
-
// NO-OP, MODIFY or RE-CREATE
|
|
118
|
-
const changeSet = ChangeSet.calculateModification(desiredParameters, filteredCurrentParameters, settings.parameterSettings);
|
|
119
|
-
return new Plan(uuidV4(), changeSet, coreParameters, statefulMode);
|
|
120
171
|
}
|
|
121
172
|
/**
|
|
122
173
|
* Only keep relevant params for the plan. We don't want to change settings that were not already
|
|
@@ -215,59 +266,8 @@ export class Plan {
|
|
|
215
266
|
}
|
|
216
267
|
// TODO: This needs to be revisited. I don't think this is valid anymore.
|
|
217
268
|
// 1. For all scenarios, there shouldn't be an apply without a plan beforehand
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (!data) {
|
|
221
|
-
throw new Error('Data is empty');
|
|
222
|
-
}
|
|
223
|
-
addDefaultValues();
|
|
224
|
-
return new Plan(uuidV4(), new ChangeSet(data.operation, data.parameters), {
|
|
225
|
-
type: data.resourceType,
|
|
226
|
-
name: data.resourceName,
|
|
227
|
-
}, data.statefulMode);
|
|
228
|
-
function addDefaultValues() {
|
|
229
|
-
Object.entries(defaultValues ?? {})
|
|
230
|
-
.forEach(([key, defaultValue]) => {
|
|
231
|
-
const configValueExists = data
|
|
232
|
-
.parameters
|
|
233
|
-
.some((p) => p.name === key);
|
|
234
|
-
// Only set default values if the value does not exist in the config
|
|
235
|
-
if (configValueExists) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
switch (data.operation) {
|
|
239
|
-
case ResourceOperation.CREATE: {
|
|
240
|
-
data.parameters.push({
|
|
241
|
-
name: key,
|
|
242
|
-
operation: ParameterOperation.ADD,
|
|
243
|
-
previousValue: null,
|
|
244
|
-
newValue: defaultValue,
|
|
245
|
-
});
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
case ResourceOperation.DESTROY: {
|
|
249
|
-
data.parameters.push({
|
|
250
|
-
name: key,
|
|
251
|
-
operation: ParameterOperation.REMOVE,
|
|
252
|
-
previousValue: defaultValue,
|
|
253
|
-
newValue: null,
|
|
254
|
-
});
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
257
|
-
case ResourceOperation.MODIFY:
|
|
258
|
-
case ResourceOperation.RECREATE:
|
|
259
|
-
case ResourceOperation.NOOP: {
|
|
260
|
-
data.parameters.push({
|
|
261
|
-
name: key,
|
|
262
|
-
operation: ParameterOperation.NOOP,
|
|
263
|
-
previousValue: defaultValue,
|
|
264
|
-
newValue: defaultValue,
|
|
265
|
-
});
|
|
266
|
-
break;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
}
|
|
269
|
+
requiresChanges() {
|
|
270
|
+
return this.changeSet.operation !== ResourceOperation.NOOP;
|
|
271
271
|
}
|
|
272
272
|
/**
|
|
273
273
|
* Convert the plan to a JSON response object
|
package/dist/plugin/plugin.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare class Plugin {
|
|
|
16
16
|
validate(data: ValidateRequestData): Promise<ValidateResponseData>;
|
|
17
17
|
plan(data: PlanRequestData): Promise<PlanResponseData>;
|
|
18
18
|
apply(data: ApplyRequestData): Promise<void>;
|
|
19
|
+
kill(): Promise<void>;
|
|
19
20
|
private resolvePlan;
|
|
20
21
|
protected crossValidateResources(configs: ResourceConfig[]): Promise<void>;
|
|
21
22
|
}
|
package/dist/plugin/plugin.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ApplyValidationError } from '../common/errors.js';
|
|
2
2
|
import { Plan } from '../plan/plan.js';
|
|
3
3
|
import { BackgroundPty } from '../pty/background-pty.js';
|
|
4
|
+
import { getPty } from '../pty/index.js';
|
|
4
5
|
import { ResourceController } from '../resource/resource-controller.js';
|
|
5
6
|
import { ptyLocalStorage } from '../utils/pty-local-storage.js';
|
|
6
7
|
export class Plugin {
|
|
@@ -97,11 +98,18 @@ export class Plugin {
|
|
|
97
98
|
throw new Error('Malformed plan with resource that cannot be found');
|
|
98
99
|
}
|
|
99
100
|
await resource.apply(plan);
|
|
100
|
-
const validationPlan = await ptyLocalStorage.run(new BackgroundPty(), async () =>
|
|
101
|
+
const validationPlan = await ptyLocalStorage.run(new BackgroundPty(), async () => {
|
|
102
|
+
const result = await resource.plan(plan.desiredConfig, plan.currentConfig, plan.statefulMode);
|
|
103
|
+
await getPty().kill();
|
|
104
|
+
return result;
|
|
105
|
+
});
|
|
101
106
|
if (validationPlan.requiresChanges()) {
|
|
102
107
|
throw new ApplyValidationError(plan);
|
|
103
108
|
}
|
|
104
109
|
}
|
|
110
|
+
async kill() {
|
|
111
|
+
await this.planPty.kill();
|
|
112
|
+
}
|
|
105
113
|
resolvePlan(data) {
|
|
106
114
|
const { plan: planRequest, planId } = data;
|
|
107
115
|
if (planId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { ApplyValidationError } from './errors.js';
|
|
3
|
-
import { Plan } from '../plan/plan.js';
|
|
4
3
|
import { testPlan } from '../utils/test-utils.test.js';
|
|
5
4
|
|
|
6
5
|
describe('Test file for errors file', () => {
|
|
@@ -21,7 +20,7 @@ describe('Test file for errors file', () => {
|
|
|
21
20
|
} catch (e) {
|
|
22
21
|
console.error(e);
|
|
23
22
|
expect(e.message).toMatch(
|
|
24
|
-
`Failed to apply changes to resource: "homebrew.first". Additional changes are needed to complete apply.
|
|
23
|
+
`Failed to apply changes to resource: "homebrew.first". Additional changes are needed to complete apply.
|
|
25
24
|
Changes remaining:
|
|
26
25
|
{
|
|
27
26
|
"operation": "destroy",
|
package/src/common/errors.ts
CHANGED
|
@@ -16,7 +16,7 @@ export class ApplyValidationError extends Error {
|
|
|
16
16
|
private static prettyPrintPlan(plan: Plan<any>): string {
|
|
17
17
|
const { operation, parameters } = plan.toResponse();
|
|
18
18
|
|
|
19
|
-
const prettyParameters = parameters.map(({ name, operation, previousValue, newValue}) => ({
|
|
19
|
+
const prettyParameters = parameters.map(({ name, operation, previousValue, newValue }) => ({
|
|
20
20
|
name,
|
|
21
21
|
operation,
|
|
22
22
|
currentValue: previousValue,
|
package/src/index.ts
CHANGED
|
@@ -16,4 +16,8 @@ export * from './utils/utils.js'
|
|
|
16
16
|
export async function runPlugin(plugin: Plugin) {
|
|
17
17
|
const messageHandler = new MessageHandler(plugin);
|
|
18
18
|
process.on('message', (message) => messageHandler.onMessage(message))
|
|
19
|
+
|
|
20
|
+
process.on('beforeExit', () => {
|
|
21
|
+
plugin.kill();
|
|
22
|
+
})
|
|
19
23
|
}
|
package/src/plan/plan.ts
CHANGED
|
@@ -70,65 +70,12 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
70
70
|
...this.changeSet.currentParameters,
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
get resourceId(): string {
|
|
75
75
|
return this.coreParameters.name
|
|
76
76
|
? `${this.coreParameters.type}.${this.coreParameters.name}`
|
|
77
77
|
: this.coreParameters.type;
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
requiresChanges(): boolean {
|
|
81
|
-
return this.changeSet.operation !== ResourceOperation.NOOP;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* When multiples of the same resource are allowed, this matching function will match a given config with one of the
|
|
86
|
-
* existing configs on the system. For example if there are multiple versions of Android Studios installed, we can use
|
|
87
|
-
* the application name and location to match it to our desired configs name and location.
|
|
88
|
-
*
|
|
89
|
-
* @param params
|
|
90
|
-
* @private
|
|
91
|
-
*/
|
|
92
|
-
private static matchCurrentParameters<T extends StringIndexedObject>(params: {
|
|
93
|
-
desiredParameters: Partial<T> | null,
|
|
94
|
-
currentParametersArray: Partial<T>[] | null,
|
|
95
|
-
stateParameters: Partial<T> | null,
|
|
96
|
-
settings: ResourceSettings<T>,
|
|
97
|
-
statefulMode: boolean,
|
|
98
|
-
}): Partial<T> | null {
|
|
99
|
-
const {
|
|
100
|
-
desiredParameters,
|
|
101
|
-
currentParametersArray,
|
|
102
|
-
stateParameters,
|
|
103
|
-
settings,
|
|
104
|
-
statefulMode
|
|
105
|
-
} = params;
|
|
106
|
-
|
|
107
|
-
if (!settings.allowMultiple) {
|
|
108
|
-
return currentParametersArray?.[0] ?? null;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (!currentParametersArray) {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (statefulMode) {
|
|
116
|
-
return stateParameters
|
|
117
|
-
? settings.allowMultiple.matcher(stateParameters, currentParametersArray)
|
|
118
|
-
: null
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return settings.allowMultiple.matcher(desiredParameters!, currentParametersArray);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* The type (id) of the resource
|
|
126
|
-
*
|
|
127
|
-
* @return string
|
|
128
|
-
*/
|
|
129
|
-
getResourceType(): string {
|
|
130
|
-
return this.coreParameters.type
|
|
131
|
-
}
|
|
132
79
|
|
|
133
80
|
static calculate<T extends StringIndexedObject>(params: {
|
|
134
81
|
desiredParameters: Partial<T> | null,
|
|
@@ -208,6 +155,126 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
208
155
|
);
|
|
209
156
|
}
|
|
210
157
|
|
|
158
|
+
/**
|
|
159
|
+
* When multiples of the same resource are allowed, this matching function will match a given config with one of the
|
|
160
|
+
* existing configs on the system. For example if there are multiple versions of Android Studios installed, we can use
|
|
161
|
+
* the application name and location to match it to our desired configs name and location.
|
|
162
|
+
*
|
|
163
|
+
* @param params
|
|
164
|
+
* @private
|
|
165
|
+
*/
|
|
166
|
+
private static matchCurrentParameters<T extends StringIndexedObject>(params: {
|
|
167
|
+
desiredParameters: Partial<T> | null,
|
|
168
|
+
currentParametersArray: Partial<T>[] | null,
|
|
169
|
+
stateParameters: Partial<T> | null,
|
|
170
|
+
settings: ResourceSettings<T>,
|
|
171
|
+
statefulMode: boolean,
|
|
172
|
+
}): Partial<T> | null {
|
|
173
|
+
const {
|
|
174
|
+
desiredParameters,
|
|
175
|
+
currentParametersArray,
|
|
176
|
+
stateParameters,
|
|
177
|
+
settings,
|
|
178
|
+
statefulMode
|
|
179
|
+
} = params;
|
|
180
|
+
|
|
181
|
+
if (!settings.allowMultiple) {
|
|
182
|
+
return currentParametersArray?.[0] ?? null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!currentParametersArray) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (statefulMode) {
|
|
190
|
+
return stateParameters
|
|
191
|
+
? settings.allowMultiple.matcher(stateParameters, currentParametersArray)
|
|
192
|
+
: null
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return settings.allowMultiple.matcher(desiredParameters!, currentParametersArray);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The type (id) of the resource
|
|
200
|
+
*
|
|
201
|
+
* @return string
|
|
202
|
+
*/
|
|
203
|
+
getResourceType(): string {
|
|
204
|
+
return this.coreParameters.type
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 2. Even if there was (maybe for testing reasons), the plan values should not be adjusted
|
|
208
|
+
static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues?: Partial<Record<keyof T, unknown>>): Plan<T> {
|
|
209
|
+
if (!data) {
|
|
210
|
+
throw new Error('Data is empty');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
addDefaultValues();
|
|
214
|
+
|
|
215
|
+
return new Plan(
|
|
216
|
+
uuidV4(),
|
|
217
|
+
new ChangeSet<T>(
|
|
218
|
+
data.operation,
|
|
219
|
+
data.parameters
|
|
220
|
+
),
|
|
221
|
+
{
|
|
222
|
+
type: data.resourceType,
|
|
223
|
+
name: data.resourceName,
|
|
224
|
+
},
|
|
225
|
+
data.statefulMode
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
function addDefaultValues(): void {
|
|
229
|
+
Object.entries(defaultValues ?? {})
|
|
230
|
+
.forEach(([key, defaultValue]) => {
|
|
231
|
+
const configValueExists = data!
|
|
232
|
+
.parameters
|
|
233
|
+
.some((p) => p.name === key);
|
|
234
|
+
|
|
235
|
+
// Only set default values if the value does not exist in the config
|
|
236
|
+
if (configValueExists) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
switch (data!.operation) {
|
|
241
|
+
case ResourceOperation.CREATE: {
|
|
242
|
+
data!.parameters.push({
|
|
243
|
+
name: key,
|
|
244
|
+
operation: ParameterOperation.ADD,
|
|
245
|
+
previousValue: null,
|
|
246
|
+
newValue: defaultValue,
|
|
247
|
+
});
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
case ResourceOperation.DESTROY: {
|
|
252
|
+
data!.parameters.push({
|
|
253
|
+
name: key,
|
|
254
|
+
operation: ParameterOperation.REMOVE,
|
|
255
|
+
previousValue: defaultValue,
|
|
256
|
+
newValue: null,
|
|
257
|
+
});
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
case ResourceOperation.MODIFY:
|
|
262
|
+
case ResourceOperation.RECREATE:
|
|
263
|
+
case ResourceOperation.NOOP: {
|
|
264
|
+
data!.parameters.push({
|
|
265
|
+
name: key,
|
|
266
|
+
operation: ParameterOperation.NOOP,
|
|
267
|
+
previousValue: defaultValue,
|
|
268
|
+
newValue: defaultValue,
|
|
269
|
+
});
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
}
|
|
277
|
+
|
|
211
278
|
/**
|
|
212
279
|
* Only keep relevant params for the plan. We don't want to change settings that were not already
|
|
213
280
|
* defined.
|
|
@@ -347,75 +414,9 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
347
414
|
|
|
348
415
|
// TODO: This needs to be revisited. I don't think this is valid anymore.
|
|
349
416
|
// 1. For all scenarios, there shouldn't be an apply without a plan beforehand
|
|
350
|
-
// 2. Even if there was (maybe for testing reasons), the plan values should not be adjusted
|
|
351
|
-
static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues?: Partial<Record<keyof T, unknown>>): Plan<T> {
|
|
352
|
-
if (!data) {
|
|
353
|
-
throw new Error('Data is empty');
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
addDefaultValues();
|
|
357
|
-
|
|
358
|
-
return new Plan(
|
|
359
|
-
uuidV4(),
|
|
360
|
-
new ChangeSet<T>(
|
|
361
|
-
data.operation,
|
|
362
|
-
data.parameters
|
|
363
|
-
),
|
|
364
|
-
{
|
|
365
|
-
type: data.resourceType,
|
|
366
|
-
name: data.resourceName,
|
|
367
|
-
},
|
|
368
|
-
data.statefulMode
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
function addDefaultValues(): void {
|
|
372
|
-
Object.entries(defaultValues ?? {})
|
|
373
|
-
.forEach(([key, defaultValue]) => {
|
|
374
|
-
const configValueExists = data!
|
|
375
|
-
.parameters
|
|
376
|
-
.some((p) => p.name === key);
|
|
377
|
-
|
|
378
|
-
// Only set default values if the value does not exist in the config
|
|
379
|
-
if (configValueExists) {
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
switch (data!.operation) {
|
|
384
|
-
case ResourceOperation.CREATE: {
|
|
385
|
-
data!.parameters.push({
|
|
386
|
-
name: key,
|
|
387
|
-
operation: ParameterOperation.ADD,
|
|
388
|
-
previousValue: null,
|
|
389
|
-
newValue: defaultValue,
|
|
390
|
-
});
|
|
391
|
-
break;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
case ResourceOperation.DESTROY: {
|
|
395
|
-
data!.parameters.push({
|
|
396
|
-
name: key,
|
|
397
|
-
operation: ParameterOperation.REMOVE,
|
|
398
|
-
previousValue: defaultValue,
|
|
399
|
-
newValue: null,
|
|
400
|
-
});
|
|
401
|
-
break;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
case ResourceOperation.MODIFY:
|
|
405
|
-
case ResourceOperation.RECREATE:
|
|
406
|
-
case ResourceOperation.NOOP: {
|
|
407
|
-
data!.parameters.push({
|
|
408
|
-
name: key,
|
|
409
|
-
operation: ParameterOperation.NOOP,
|
|
410
|
-
previousValue: defaultValue,
|
|
411
|
-
newValue: defaultValue,
|
|
412
|
-
});
|
|
413
|
-
break;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
417
|
|
|
418
|
+
requiresChanges(): boolean {
|
|
419
|
+
return this.changeSet.operation !== ResourceOperation.NOOP;
|
|
419
420
|
}
|
|
420
421
|
|
|
421
422
|
/**
|
|
@@ -7,7 +7,6 @@ import { spy } from 'sinon';
|
|
|
7
7
|
import { ResourceSettings } from '../resource/resource-settings.js';
|
|
8
8
|
import { TestConfig } from '../utils/test-utils.test.js';
|
|
9
9
|
import { ApplyValidationError } from '../common/errors.js';
|
|
10
|
-
import { ResourceController } from '../resource/resource-controller.js';
|
|
11
10
|
import { getPty } from '../pty/index.js';
|
|
12
11
|
|
|
13
12
|
interface TestConfig extends StringIndexedObject {
|
|
@@ -42,7 +41,7 @@ class TestResource extends Resource<TestConfig> {
|
|
|
42
41
|
|
|
43
42
|
describe('Plugin tests', () => {
|
|
44
43
|
it('Can apply resource', async () => {
|
|
45
|
-
const resource= spy(new class extends TestResource {
|
|
44
|
+
const resource = spy(new class extends TestResource {
|
|
46
45
|
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
47
46
|
return {
|
|
48
47
|
propA: 'abc',
|
package/src/plugin/plugin.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import { ApplyValidationError } from '../common/errors.js';
|
|
17
17
|
import { Plan } from '../plan/plan.js';
|
|
18
18
|
import { BackgroundPty } from '../pty/background-pty.js';
|
|
19
|
+
import { getPty } from '../pty/index.js';
|
|
19
20
|
import { Resource } from '../resource/resource.js';
|
|
20
21
|
import { ResourceController } from '../resource/resource-controller.js';
|
|
21
22
|
import { ptyLocalStorage } from '../utils/pty-local-storage.js';
|
|
@@ -127,7 +128,7 @@ export class Plugin {
|
|
|
127
128
|
data.desired ?? null,
|
|
128
129
|
data.state ?? null,
|
|
129
130
|
data.isStateful
|
|
130
|
-
|
|
131
|
+
))
|
|
131
132
|
|
|
132
133
|
this.planStorage.set(plan.id, plan);
|
|
133
134
|
|
|
@@ -148,17 +149,26 @@ export class Plugin {
|
|
|
148
149
|
|
|
149
150
|
await resource.apply(plan);
|
|
150
151
|
|
|
151
|
-
const validationPlan = await ptyLocalStorage.run(new BackgroundPty(), async () =>
|
|
152
|
-
plan
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
152
|
+
const validationPlan = await ptyLocalStorage.run(new BackgroundPty(), async () => {
|
|
153
|
+
const result = await resource.plan(
|
|
154
|
+
plan.desiredConfig,
|
|
155
|
+
plan.currentConfig,
|
|
156
|
+
plan.statefulMode
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
await getPty().kill();
|
|
160
|
+
return result;
|
|
161
|
+
})
|
|
162
|
+
|
|
157
163
|
if (validationPlan.requiresChanges()) {
|
|
158
164
|
throw new ApplyValidationError(plan);
|
|
159
165
|
}
|
|
160
166
|
}
|
|
161
167
|
|
|
168
|
+
async kill() {
|
|
169
|
+
await this.planPty.kill();
|
|
170
|
+
}
|
|
171
|
+
|
|
162
172
|
private resolvePlan(data: ApplyRequestData): Plan<ResourceConfig> {
|
|
163
173
|
const { plan: planRequest, planId } = data;
|
|
164
174
|
|
|
@@ -179,5 +189,4 @@ export class Plugin {
|
|
|
179
189
|
}
|
|
180
190
|
|
|
181
191
|
protected async crossValidateResources(configs: ResourceConfig[]): Promise<void> {}
|
|
182
|
-
|
|
183
192
|
}
|