codify-plugin-lib 1.0.71 → 1.0.72
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/entities/plugin.js +6 -8
- package/dist/entities/resource-types.d.ts +0 -4
- package/dist/entities/resource.d.ts +4 -4
- package/dist/entities/resource.js +29 -8
- package/package.json +2 -2
- package/src/entities/plugin.test.ts +22 -112
- package/src/entities/plugin.ts +7 -9
- package/src/entities/resource-parameters.test.ts +5 -5
- package/src/entities/resource-stateful-mode.test.ts +4 -4
- package/src/entities/resource-types.ts +0 -5
- package/src/entities/resource.test.ts +9 -18
- package/src/entities/resource.ts +47 -10
- package/src/entities/stateful-parameter.test.ts +4 -5
package/dist/entities/plugin.js
CHANGED
|
@@ -31,17 +31,15 @@ export class Plugin {
|
|
|
31
31
|
if (!this.resources.has(config.type)) {
|
|
32
32
|
throw new Error(`Resource type not found: ${config.type}`);
|
|
33
33
|
}
|
|
34
|
-
const { parameters } = splitUserConfig(config);
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
resourceName: config.name,
|
|
40
|
-
});
|
|
34
|
+
const { parameters, resourceMetadata } = splitUserConfig(config);
|
|
35
|
+
const validation = await this.resources
|
|
36
|
+
.get(config.type)
|
|
37
|
+
.validate(parameters, resourceMetadata);
|
|
38
|
+
validationResults.push(validation);
|
|
41
39
|
}
|
|
42
40
|
await this.crossValidateResources(data.configs);
|
|
43
41
|
return {
|
|
44
|
-
validationResults
|
|
42
|
+
resourceValidations: validationResults
|
|
45
43
|
};
|
|
46
44
|
}
|
|
47
45
|
async plan(data) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import Ajv from 'ajv';
|
|
2
2
|
import { ValidateFunction } from 'ajv/dist/2020.js';
|
|
3
|
-
import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
|
|
3
|
+
import { ResourceConfig, StringIndexedObject, ValidateResponseData } from 'codify-schemas';
|
|
4
4
|
import { ParameterChange } from './change-set.js';
|
|
5
5
|
import { Plan } from './plan.js';
|
|
6
6
|
import { CreatePlan, DestroyPlan, ModifyPlan, ParameterOptions } from './plan-types.js';
|
|
7
7
|
import { ResourceOptions } from './resource-options.js';
|
|
8
|
-
import { ResourceParameterOptions
|
|
8
|
+
import { ResourceParameterOptions } from './resource-types.js';
|
|
9
9
|
import { StatefulParameter } from './stateful-parameter.js';
|
|
10
10
|
import { TransformParameter } from './transform-parameter.js';
|
|
11
11
|
export declare abstract class Resource<T extends StringIndexedObject> {
|
|
@@ -23,7 +23,7 @@ export declare abstract class Resource<T extends StringIndexedObject> {
|
|
|
23
23
|
protected schemaValidator?: ValidateFunction;
|
|
24
24
|
protected constructor(options: ResourceOptions<T>);
|
|
25
25
|
onInitialize(): Promise<void>;
|
|
26
|
-
|
|
26
|
+
validate(parameters: Partial<T>, resourceMetaData: ResourceConfig): Promise<ValidateResponseData['resourceValidations'][0]>;
|
|
27
27
|
plan(desiredConfig: Partial<T> & ResourceConfig | null, currentConfig?: Partial<T> & ResourceConfig | null, statefulMode?: boolean): Promise<Plan<T>>;
|
|
28
28
|
apply(plan: Plan<T>): Promise<void>;
|
|
29
29
|
private _applyCreate;
|
|
@@ -35,7 +35,7 @@ export declare abstract class Resource<T extends StringIndexedObject> {
|
|
|
35
35
|
private refreshNonStatefulParameters;
|
|
36
36
|
private refreshStatefulParameters;
|
|
37
37
|
private validatePlanInputs;
|
|
38
|
-
|
|
38
|
+
customValidation(parameters: Partial<T>): Promise<void>;
|
|
39
39
|
abstract refresh(parameters: Partial<T>): Promise<Partial<T> | null>;
|
|
40
40
|
abstract applyCreate(plan: CreatePlan<T>): Promise<void>;
|
|
41
41
|
applyModify(pc: ParameterChange<T>, plan: ModifyPlan<T>): Promise<void>;
|
|
@@ -37,17 +37,42 @@ export class Resource {
|
|
|
37
37
|
this.transformParameterOrder = parser.transformParameterOrder;
|
|
38
38
|
}
|
|
39
39
|
async onInitialize() { }
|
|
40
|
-
async
|
|
40
|
+
async validate(parameters, resourceMetaData) {
|
|
41
41
|
if (this.schemaValidator) {
|
|
42
42
|
const isValid = this.schemaValidator(parameters);
|
|
43
43
|
if (!isValid) {
|
|
44
44
|
return {
|
|
45
|
-
|
|
45
|
+
resourceType: resourceMetaData.type,
|
|
46
|
+
resourceName: resourceMetaData.name,
|
|
47
|
+
schemaValidationErrors: this.schemaValidator?.errors ?? [],
|
|
46
48
|
isValid: false,
|
|
47
49
|
};
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
|
-
|
|
52
|
+
let isValid = true;
|
|
53
|
+
let customValidationErrorMessage = undefined;
|
|
54
|
+
try {
|
|
55
|
+
await this.customValidation(parameters);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
isValid = false;
|
|
59
|
+
customValidationErrorMessage = err.message;
|
|
60
|
+
}
|
|
61
|
+
if (!isValid) {
|
|
62
|
+
return {
|
|
63
|
+
resourceType: resourceMetaData.type,
|
|
64
|
+
resourceName: resourceMetaData.name,
|
|
65
|
+
schemaValidationErrors: this.schemaValidator?.errors ?? [],
|
|
66
|
+
customValidationErrorMessage,
|
|
67
|
+
isValid: false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
resourceType: resourceMetaData.type,
|
|
72
|
+
resourceName: resourceMetaData.name,
|
|
73
|
+
schemaValidationErrors: [],
|
|
74
|
+
isValid: true,
|
|
75
|
+
};
|
|
51
76
|
}
|
|
52
77
|
async plan(desiredConfig, currentConfig = null, statefulMode = false) {
|
|
53
78
|
this.validatePlanInputs(desiredConfig, currentConfig, statefulMode);
|
|
@@ -221,11 +246,7 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`);
|
|
|
221
246
|
throw new Error('Desired config must be provided in non-stateful mode');
|
|
222
247
|
}
|
|
223
248
|
}
|
|
224
|
-
async
|
|
225
|
-
return {
|
|
226
|
-
isValid: true,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
249
|
+
async customValidation(parameters) { }
|
|
229
250
|
;
|
|
230
251
|
async applyModify(pc, plan) { }
|
|
231
252
|
;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codify-plugin-lib",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.72",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"ajv": "^8.12.0",
|
|
16
16
|
"ajv-formats": "^2.1.1",
|
|
17
|
-
"codify-schemas": "1.0.
|
|
17
|
+
"codify-schemas": "1.0.42",
|
|
18
18
|
"@npmcli/promise-spawn": "^7.0.1"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
@@ -3,8 +3,7 @@ import { Plugin } from './plugin.js';
|
|
|
3
3
|
import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
|
|
4
4
|
import { Resource } from './resource.js';
|
|
5
5
|
import { Plan } from './plan.js';
|
|
6
|
-
import {
|
|
7
|
-
import { ApplyValidationError } from './errors.js';
|
|
6
|
+
import { spy } from 'sinon';
|
|
8
7
|
|
|
9
8
|
interface TestConfig extends StringIndexedObject {
|
|
10
9
|
propA: string;
|
|
@@ -27,36 +26,19 @@ class TestResource extends Resource<TestConfig> {
|
|
|
27
26
|
return Promise.resolve(undefined);
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
async refresh(
|
|
29
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
31
30
|
return {
|
|
32
31
|
propA: 'a',
|
|
33
32
|
propB: 10,
|
|
34
33
|
propC: 'c',
|
|
35
34
|
};
|
|
36
35
|
}
|
|
37
|
-
|
|
38
|
-
async validateResource(config: unknown): Promise<ValidationResult> {
|
|
39
|
-
return {
|
|
40
|
-
isValid: true
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
36
|
}
|
|
44
37
|
|
|
45
38
|
describe('Plugin tests', () => {
|
|
46
|
-
it('
|
|
47
|
-
const resource= new
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Refresh has to line up with desired for the apply to go through
|
|
52
|
-
async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
|
|
53
|
-
return {
|
|
54
|
-
propA: 'abc'
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const plugin = Plugin.create('testPlugin', [resource])
|
|
39
|
+
it('Can apply resource', async () => {
|
|
40
|
+
const resource= spy(new TestResource())
|
|
41
|
+
const plugin = Plugin.create('testPlugin', [resource as any])
|
|
60
42
|
|
|
61
43
|
const plan = {
|
|
62
44
|
operation: ResourceOperation.CREATE,
|
|
@@ -66,45 +48,13 @@ describe('Plugin tests', () => {
|
|
|
66
48
|
]
|
|
67
49
|
};
|
|
68
50
|
|
|
69
|
-
// If this doesn't throw then it passes the test
|
|
70
51
|
await plugin.apply({ plan });
|
|
52
|
+
expect(resource.applyCreate.calledOnce).to.be.true;
|
|
71
53
|
});
|
|
72
54
|
|
|
73
|
-
it('
|
|
74
|
-
const resource = new
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Return null to indicate that the resource was not created
|
|
79
|
-
async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
const plugin = Plugin.create('testPlugin', [resource])
|
|
84
|
-
|
|
85
|
-
const plan = {
|
|
86
|
-
operation: ResourceOperation.CREATE,
|
|
87
|
-
resourceType: 'testResource',
|
|
88
|
-
parameters: [
|
|
89
|
-
{ name: 'propA', operation: ParameterOperation.ADD, newValue: 'abc', previousValue: null },
|
|
90
|
-
]
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
await expect(async () => plugin.apply({ plan })).rejects.toThrowError(expect.any(ApplyValidationError));
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('Validates that deletes were successfully applied', async () => {
|
|
97
|
-
const resource = new class extends TestResource {
|
|
98
|
-
async applyCreate(plan: Plan<TestConfig>): Promise<void> {
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Return null to indicate that the resource was deleted
|
|
102
|
-
async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const testPlugin = Plugin.create('testPlugin', [resource])
|
|
55
|
+
it('Can destroy resource', async () => {
|
|
56
|
+
const resource = spy(new TestResource());
|
|
57
|
+
const testPlugin = Plugin.create('testPlugin', [resource as any])
|
|
108
58
|
|
|
109
59
|
const plan = {
|
|
110
60
|
operation: ResourceOperation.DESTROY,
|
|
@@ -114,46 +64,13 @@ describe('Plugin tests', () => {
|
|
|
114
64
|
]
|
|
115
65
|
};
|
|
116
66
|
|
|
117
|
-
// If this doesn't throw then it passes the test
|
|
118
67
|
await testPlugin.apply({ plan })
|
|
68
|
+
expect(resource.applyDestroy.calledOnce).to.be.true;
|
|
119
69
|
});
|
|
120
70
|
|
|
121
|
-
it('
|
|
122
|
-
const resource = new
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Return a value to indicate that the resource still exists
|
|
127
|
-
async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
|
|
128
|
-
return { propA: 'abc' };
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const testPlugin = Plugin.create('testPlugin', [resource])
|
|
133
|
-
|
|
134
|
-
const plan = {
|
|
135
|
-
operation: ResourceOperation.DESTROY,
|
|
136
|
-
resourceType: 'testResource',
|
|
137
|
-
parameters: [
|
|
138
|
-
{ name: 'propA', operation: ParameterOperation.REMOVE, newValue: null, previousValue: 'abc' },
|
|
139
|
-
]
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
// If this doesn't throw then it passes the test
|
|
143
|
-
expect(async () => await testPlugin.apply({ plan })).rejects.toThrowError(expect.any(ApplyValidationError));
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('Validates that re-create was successfully applied', async () => {
|
|
147
|
-
const resource = new class extends TestResource {
|
|
148
|
-
async applyCreate(plan: Plan<TestConfig>): Promise<void> {
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
|
|
152
|
-
return { propA: 'def'};
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const testPlugin = Plugin.create('testPlugin', [resource])
|
|
71
|
+
it('Can re-create resource', async () => {
|
|
72
|
+
const resource = spy(new TestResource())
|
|
73
|
+
const testPlugin = Plugin.create('testPlugin', [resource as any])
|
|
157
74
|
|
|
158
75
|
const plan = {
|
|
159
76
|
operation: ResourceOperation.RECREATE,
|
|
@@ -163,31 +80,24 @@ describe('Plugin tests', () => {
|
|
|
163
80
|
]
|
|
164
81
|
};
|
|
165
82
|
|
|
166
|
-
// If this doesn't throw then it passes the test
|
|
167
83
|
await testPlugin.apply({ plan })
|
|
84
|
+
expect(resource.applyDestroy.calledOnce).to.be.true;
|
|
85
|
+
expect(resource.applyCreate.calledOnce).to.be.true;
|
|
168
86
|
});
|
|
169
87
|
|
|
170
|
-
it('
|
|
171
|
-
const resource = new
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
|
|
176
|
-
return { propA: 'abc' };
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const testPlugin = Plugin.create('testPlugin', [resource])
|
|
88
|
+
it('Can modify resource', async () => {
|
|
89
|
+
const resource = spy(new TestResource())
|
|
90
|
+
const testPlugin = Plugin.create('testPlugin', [resource as any])
|
|
181
91
|
|
|
182
92
|
const plan = {
|
|
183
|
-
operation: ResourceOperation.
|
|
93
|
+
operation: ResourceOperation.MODIFY,
|
|
184
94
|
resourceType: 'testResource',
|
|
185
95
|
parameters: [
|
|
186
|
-
{ name: 'propA', operation: ParameterOperation.
|
|
96
|
+
{ name: 'propA', operation: ParameterOperation.MODIFY, newValue: 'def', previousValue: 'abc' },
|
|
187
97
|
]
|
|
188
98
|
};
|
|
189
99
|
|
|
190
|
-
|
|
191
|
-
expect(
|
|
100
|
+
await testPlugin.apply({ plan })
|
|
101
|
+
expect(resource.applyModify.calledOnce).to.be.true;
|
|
192
102
|
});
|
|
193
103
|
});
|
package/src/entities/plugin.ts
CHANGED
|
@@ -50,19 +50,17 @@ export class Plugin {
|
|
|
50
50
|
throw new Error(`Resource type not found: ${config.type}`);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const { parameters } = splitUserConfig(config);
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
resourceName: config.name,
|
|
60
|
-
});
|
|
53
|
+
const { parameters, resourceMetadata } = splitUserConfig(config);
|
|
54
|
+
const validation = await this.resources
|
|
55
|
+
.get(config.type)!
|
|
56
|
+
.validate(parameters, resourceMetadata);
|
|
57
|
+
|
|
58
|
+
validationResults.push(validation);
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
await this.crossValidateResources(data.configs);
|
|
64
62
|
return {
|
|
65
|
-
validationResults
|
|
63
|
+
resourceValidations: validationResults
|
|
66
64
|
};
|
|
67
65
|
}
|
|
68
66
|
|
|
@@ -471,9 +471,9 @@ describe('Resource parameter tests', () => {
|
|
|
471
471
|
const plan = await resource.plan({ type: 'resourceType', propC: 'abc' } as any);
|
|
472
472
|
|
|
473
473
|
expect(resource.refresh.called).to.be.true;
|
|
474
|
-
expect(resource.refresh.getCall(0).firstArg
|
|
475
|
-
expect(resource.refresh.getCall(0).firstArg
|
|
476
|
-
expect(resource.refresh.getCall(0).firstArg
|
|
474
|
+
expect(resource.refresh.getCall(0).firstArg['propA']).to.exist;
|
|
475
|
+
expect(resource.refresh.getCall(0).firstArg['propB']).to.exist;
|
|
476
|
+
expect(resource.refresh.getCall(0).firstArg['propC']).to.not.exist;
|
|
477
477
|
|
|
478
478
|
expect(plan.desiredConfig?.propA).to.eq('propA');
|
|
479
479
|
expect(plan.desiredConfig?.propB).to.eq(10);
|
|
@@ -513,8 +513,8 @@ describe('Resource parameter tests', () => {
|
|
|
513
513
|
const plan = await resource.plan({ type: 'resourceType', propA: 'propA', propB: 10 } as any);
|
|
514
514
|
|
|
515
515
|
expect(transformParameter.transform.called).to.be.false;
|
|
516
|
-
expect(resource.refresh.getCall(0).firstArg
|
|
517
|
-
expect(resource.refresh.getCall(0).firstArg
|
|
516
|
+
expect(resource.refresh.getCall(0).firstArg['propA']).to.exist;
|
|
517
|
+
expect(resource.refresh.getCall(0).firstArg['propB']).to.exist;
|
|
518
518
|
|
|
519
519
|
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
520
520
|
})
|
|
@@ -12,7 +12,7 @@ describe('Resource tests for stateful plans', () => {
|
|
|
12
12
|
super({ type: 'resource' });
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
async refresh(
|
|
15
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
16
16
|
return {
|
|
17
17
|
propA: 'propADifferent',
|
|
18
18
|
propB: undefined,
|
|
@@ -61,7 +61,7 @@ describe('Resource tests for stateful plans', () => {
|
|
|
61
61
|
super({ type: 'resource' });
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
async refresh(
|
|
64
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -113,7 +113,7 @@ describe('Resource tests for stateful plans', () => {
|
|
|
113
113
|
super({ type: 'resource' });
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
async refresh(
|
|
116
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
117
117
|
return {
|
|
118
118
|
propA: 'propA',
|
|
119
119
|
propC: 'propC',
|
|
@@ -184,7 +184,7 @@ describe('Resource tests for stateful plans', () => {
|
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
async refresh(
|
|
187
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
188
188
|
return {
|
|
189
189
|
propA: 'propA',
|
|
190
190
|
propC: 'propC',
|
|
@@ -3,7 +3,6 @@ import { ResourceOperation, StringIndexedObject } from 'codify-schemas';
|
|
|
3
3
|
import { spy } from 'sinon';
|
|
4
4
|
import { Plan } from './plan.js';
|
|
5
5
|
import { describe, expect, it } from 'vitest'
|
|
6
|
-
import { ValidationResult } from './resource-types.js';
|
|
7
6
|
import { StatefulParameter } from './stateful-parameter.js';
|
|
8
7
|
import { ResourceOptions } from './resource-options.js';
|
|
9
8
|
import { CreatePlan, DestroyPlan, ModifyPlan } from './plan-types.js';
|
|
@@ -28,19 +27,13 @@ export class TestResource extends Resource<TestConfig> {
|
|
|
28
27
|
return Promise.resolve(undefined);
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
async refresh(
|
|
30
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
32
31
|
return {
|
|
33
32
|
propA: 'a',
|
|
34
33
|
propB: 10,
|
|
35
34
|
propC: 'c',
|
|
36
35
|
};
|
|
37
36
|
}
|
|
38
|
-
|
|
39
|
-
async validateResource(config: unknown): Promise<ValidationResult> {
|
|
40
|
-
return {
|
|
41
|
-
isValid: true
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
37
|
}
|
|
45
38
|
|
|
46
39
|
describe('Resource tests', () => {
|
|
@@ -298,9 +291,8 @@ describe('Resource tests', () => {
|
|
|
298
291
|
}
|
|
299
292
|
|
|
300
293
|
// @ts-ignore
|
|
301
|
-
async refresh(desired:
|
|
302
|
-
expect(desired
|
|
303
|
-
expect(desired.get('propA')).to.be.eq('propADefault');
|
|
294
|
+
async refresh(desired: Partial<TestConfig>): Promise<Partial<TestConfig>> {
|
|
295
|
+
expect(desired['propA']).to.be.eq('propADefault');
|
|
304
296
|
|
|
305
297
|
return {
|
|
306
298
|
propA: 'propAAfter'
|
|
@@ -325,11 +317,11 @@ describe('Resource tests', () => {
|
|
|
325
317
|
});
|
|
326
318
|
}
|
|
327
319
|
|
|
328
|
-
async refresh(
|
|
329
|
-
expect(
|
|
320
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
321
|
+
expect(parameters['propE']).to.exist;
|
|
330
322
|
|
|
331
323
|
return {
|
|
332
|
-
propE:
|
|
324
|
+
propE: parameters['propE'],
|
|
333
325
|
};
|
|
334
326
|
}
|
|
335
327
|
}
|
|
@@ -374,9 +366,8 @@ describe('Resource tests', () => {
|
|
|
374
366
|
}
|
|
375
367
|
|
|
376
368
|
// @ts-ignore
|
|
377
|
-
async refresh(
|
|
378
|
-
expect(
|
|
379
|
-
expect(desired.get('propA')).to.be.eq('propA');
|
|
369
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig>> {
|
|
370
|
+
expect(parameters['propA']).to.be.eq('propA');
|
|
380
371
|
|
|
381
372
|
return {
|
|
382
373
|
propA: 'propAAfter'
|
|
@@ -413,7 +404,7 @@ describe('Resource tests', () => {
|
|
|
413
404
|
super({ type: 'type' });
|
|
414
405
|
}
|
|
415
406
|
|
|
416
|
-
async refresh(
|
|
407
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
417
408
|
return null;
|
|
418
409
|
}
|
|
419
410
|
|
package/src/entities/resource.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import Ajv from 'ajv';
|
|
2
2
|
import Ajv2020, { ValidateFunction } from 'ajv/dist/2020.js';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ParameterOperation,
|
|
5
|
+
ResourceConfig,
|
|
6
|
+
ResourceOperation,
|
|
7
|
+
StringIndexedObject,
|
|
8
|
+
ValidateResponseData,
|
|
9
|
+
} from 'codify-schemas';
|
|
4
10
|
|
|
5
11
|
import { setsEqual, splitUserConfig } from '../utils/utils.js';
|
|
6
12
|
import { ParameterChange } from './change-set.js';
|
|
7
13
|
import { Plan } from './plan.js';
|
|
8
14
|
import { CreatePlan, DestroyPlan, ModifyPlan, ParameterOptions, PlanOptions } from './plan-types.js';
|
|
9
15
|
import { ResourceOptions, ResourceOptionsParser } from './resource-options.js';
|
|
10
|
-
import { ResourceParameterOptions
|
|
16
|
+
import { ResourceParameterOptions } from './resource-types.js';
|
|
11
17
|
import { StatefulParameter } from './stateful-parameter.js';
|
|
12
18
|
import { TransformParameter } from './transform-parameter.js';
|
|
13
19
|
|
|
@@ -60,19 +66,48 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
60
66
|
|
|
61
67
|
async onInitialize(): Promise<void> {}
|
|
62
68
|
|
|
63
|
-
async
|
|
69
|
+
async validate(
|
|
70
|
+
parameters: Partial<T>,
|
|
71
|
+
resourceMetaData: ResourceConfig
|
|
72
|
+
): Promise<ValidateResponseData['resourceValidations'][0]> {
|
|
64
73
|
if (this.schemaValidator) {
|
|
65
74
|
const isValid = this.schemaValidator(parameters);
|
|
66
75
|
|
|
67
76
|
if (!isValid) {
|
|
68
77
|
return {
|
|
69
|
-
|
|
78
|
+
resourceType: resourceMetaData.type,
|
|
79
|
+
resourceName: resourceMetaData.name,
|
|
80
|
+
schemaValidationErrors: this.schemaValidator?.errors ?? [],
|
|
70
81
|
isValid: false,
|
|
71
82
|
}
|
|
72
83
|
}
|
|
73
84
|
}
|
|
74
85
|
|
|
75
|
-
|
|
86
|
+
let isValid = true;
|
|
87
|
+
let customValidationErrorMessage = undefined;
|
|
88
|
+
try {
|
|
89
|
+
await this.customValidation(parameters);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
isValid = false;
|
|
92
|
+
customValidationErrorMessage = (err as Error).message;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!isValid) {
|
|
96
|
+
return {
|
|
97
|
+
resourceType: resourceMetaData.type,
|
|
98
|
+
resourceName: resourceMetaData.name,
|
|
99
|
+
schemaValidationErrors: this.schemaValidator?.errors ?? [],
|
|
100
|
+
customValidationErrorMessage,
|
|
101
|
+
isValid: false,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
resourceType: resourceMetaData.type,
|
|
107
|
+
resourceName: resourceMetaData.name,
|
|
108
|
+
schemaValidationErrors: [],
|
|
109
|
+
isValid: true,
|
|
110
|
+
}
|
|
76
111
|
}
|
|
77
112
|
|
|
78
113
|
// TODO: Currently stateful mode expects that the currentConfig does not need any additional transformations (default and transform parameters)
|
|
@@ -342,11 +377,13 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
|
342
377
|
}
|
|
343
378
|
}
|
|
344
379
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
380
|
+
/**
|
|
381
|
+
* Add custom validation logic in-addition to the default schema validation.
|
|
382
|
+
* In this method throw an error if the object did not validate. The message of the
|
|
383
|
+
* error will be shown to the user.
|
|
384
|
+
* @param parameters
|
|
385
|
+
*/
|
|
386
|
+
async customValidation(parameters: Partial<T>): Promise<void> {};
|
|
350
387
|
|
|
351
388
|
abstract refresh(parameters: Partial<T>): Promise<Partial<T> | null>;
|
|
352
389
|
|
|
@@ -22,7 +22,6 @@ class TestArrayParameter extends ArrayStatefulParameter<TestConfig, string> {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
25
|
describe('Stateful parameter tests', () => {
|
|
27
26
|
it('applyAddItem is called the correct number of times', async () => {
|
|
28
27
|
const plan = Plan.create<TestConfig>(
|
|
@@ -36,7 +35,7 @@ describe('Stateful parameter tests', () => {
|
|
|
36
35
|
expect(plan.changeSet.parameterChanges.length).to.eq(1);
|
|
37
36
|
|
|
38
37
|
const testParameter = spy(new TestArrayParameter());
|
|
39
|
-
await testParameter.applyAdd(plan.desiredConfig
|
|
38
|
+
await testParameter.applyAdd(plan.desiredConfig!.propA, plan);
|
|
40
39
|
|
|
41
40
|
expect(testParameter.applyAddItem.callCount).to.eq(3);
|
|
42
41
|
expect(testParameter.applyRemoveItem.called).to.be.false;
|
|
@@ -54,7 +53,7 @@ describe('Stateful parameter tests', () => {
|
|
|
54
53
|
expect(plan.changeSet.parameterChanges.length).to.eq(1);
|
|
55
54
|
|
|
56
55
|
const testParameter = spy(new TestArrayParameter());
|
|
57
|
-
await testParameter.applyRemove(plan.currentConfig
|
|
56
|
+
await testParameter.applyRemove(plan.currentConfig!.propA, plan);
|
|
58
57
|
|
|
59
58
|
expect(testParameter.applyAddItem.called).to.be.false;
|
|
60
59
|
expect(testParameter.applyRemoveItem.callCount).to.eq(3);
|
|
@@ -77,7 +76,7 @@ describe('Stateful parameter tests', () => {
|
|
|
77
76
|
})
|
|
78
77
|
|
|
79
78
|
const testParameter = spy(new TestArrayParameter());
|
|
80
|
-
await testParameter.applyModify(plan.desiredConfig
|
|
79
|
+
await testParameter.applyModify(plan.desiredConfig!.propA, plan.currentConfig!.propA, false, plan);
|
|
81
80
|
|
|
82
81
|
expect(testParameter.applyAddItem.calledThrice).to.be.true;
|
|
83
82
|
expect(testParameter.applyRemoveItem.called).to.be.false;
|
|
@@ -107,7 +106,7 @@ describe('Stateful parameter tests', () => {
|
|
|
107
106
|
}
|
|
108
107
|
});
|
|
109
108
|
|
|
110
|
-
await testParameter.applyModify(plan.desiredConfig
|
|
109
|
+
await testParameter.applyModify(plan.desiredConfig!.propA, plan.currentConfig!.propA, false, plan);
|
|
111
110
|
|
|
112
111
|
expect(testParameter.applyAddItem.calledOnce).to.be.true;
|
|
113
112
|
expect(testParameter.applyRemoveItem.called).to.be.false;
|