codify-plugin-lib 1.0.47 → 1.0.49
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/plan.d.ts +1 -1
- package/dist/entities/plan.js +44 -6
- package/dist/entities/plugin.js +5 -1
- package/dist/entities/resource-types.d.ts +1 -0
- package/dist/entities/resource.d.ts +4 -1
- package/dist/entities/resource.js +21 -2
- package/package.json +1 -1
- package/src/entities/plan.test.ts +151 -0
- package/src/entities/plan.ts +50 -9
- package/src/entities/plugin.ts +6 -1
- package/src/entities/resource-types.ts +4 -0
- package/src/entities/resource.test.ts +57 -0
- package/src/entities/resource.ts +30 -4
package/dist/entities/plan.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare class Plan<T extends StringIndexedObject> {
|
|
|
8
8
|
constructor(id: string, changeSet: ChangeSet<T>, resourceMetadata: ResourceConfig);
|
|
9
9
|
static create<T extends StringIndexedObject>(desiredParameters: Partial<T> | null, currentParameters: Partial<T> | null, resourceMetadata: ResourceConfig, configuration: PlanConfiguration<T>): Plan<T>;
|
|
10
10
|
getResourceType(): string;
|
|
11
|
-
static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan']): Plan<T>;
|
|
11
|
+
static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues: Partial<Record<keyof T, unknown>>): Plan<T>;
|
|
12
12
|
get desiredConfig(): T;
|
|
13
13
|
get currentConfig(): T;
|
|
14
14
|
toResponse(): PlanResponseData;
|
package/dist/entities/plan.js
CHANGED
|
@@ -45,18 +45,56 @@ export class Plan {
|
|
|
45
45
|
getResourceType() {
|
|
46
46
|
return this.resourceMetadata.type;
|
|
47
47
|
}
|
|
48
|
-
static fromResponse(data) {
|
|
48
|
+
static fromResponse(data, defaultValues) {
|
|
49
49
|
if (!data) {
|
|
50
50
|
throw new Error('Data is empty');
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
previousValue: null,
|
|
55
|
-
}))), {
|
|
52
|
+
addDefaultValues();
|
|
53
|
+
return new Plan(randomUUID(), new ChangeSet(data.operation, data.parameters), {
|
|
56
54
|
type: data.resourceType,
|
|
57
55
|
name: data.resourceName,
|
|
58
|
-
...(data.parameters.reduce((prev, { name, newValue }) => Object.assign(prev, { [name]: newValue }), {}))
|
|
59
56
|
});
|
|
57
|
+
function addDefaultValues() {
|
|
58
|
+
Object.entries(defaultValues)
|
|
59
|
+
.forEach(([key, defaultValue]) => {
|
|
60
|
+
const configValueExists = data
|
|
61
|
+
?.parameters
|
|
62
|
+
.find((p) => p.name === key) !== undefined;
|
|
63
|
+
if (!configValueExists) {
|
|
64
|
+
switch (data?.operation) {
|
|
65
|
+
case ResourceOperation.CREATE: {
|
|
66
|
+
data?.parameters.push({
|
|
67
|
+
name: key,
|
|
68
|
+
operation: ParameterOperation.ADD,
|
|
69
|
+
previousValue: null,
|
|
70
|
+
newValue: defaultValue,
|
|
71
|
+
});
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case ResourceOperation.DESTROY: {
|
|
75
|
+
data?.parameters.push({
|
|
76
|
+
name: key,
|
|
77
|
+
operation: ParameterOperation.REMOVE,
|
|
78
|
+
previousValue: defaultValue,
|
|
79
|
+
newValue: null,
|
|
80
|
+
});
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case ResourceOperation.MODIFY:
|
|
84
|
+
case ResourceOperation.RECREATE:
|
|
85
|
+
case ResourceOperation.NOOP: {
|
|
86
|
+
data?.parameters.push({
|
|
87
|
+
name: key,
|
|
88
|
+
operation: ParameterOperation.NOOP,
|
|
89
|
+
previousValue: defaultValue,
|
|
90
|
+
newValue: defaultValue,
|
|
91
|
+
});
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
60
98
|
}
|
|
61
99
|
get desiredConfig() {
|
|
62
100
|
return {
|
package/dist/entities/plugin.js
CHANGED
|
@@ -65,7 +65,11 @@ export class Plugin {
|
|
|
65
65
|
}
|
|
66
66
|
return this.planStorage.get(planId);
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
if (!planRequest?.resourceType || !this.resources.has(planRequest.resourceType)) {
|
|
69
|
+
throw new Error('Malformed plan. Resource type must be supplied');
|
|
70
|
+
}
|
|
71
|
+
const resource = this.resources.get(planRequest.resourceType);
|
|
72
|
+
return Plan.fromResponse(data.plan, resource?.defaultValues);
|
|
69
73
|
}
|
|
70
74
|
async crossValidateResources(configs) { }
|
|
71
75
|
}
|
|
@@ -4,6 +4,7 @@ export type ErrorMessage = string;
|
|
|
4
4
|
export interface ResourceParameterConfiguration {
|
|
5
5
|
planOperation?: ResourceOperation.MODIFY | ResourceOperation.RECREATE;
|
|
6
6
|
isEqual?: (desired: any, current: any) => boolean;
|
|
7
|
+
defaultValue?: unknown;
|
|
7
8
|
}
|
|
8
9
|
export interface ResourceConfiguration<T extends StringIndexedObject> {
|
|
9
10
|
type: string;
|
|
@@ -9,6 +9,7 @@ export declare abstract class Resource<T extends StringIndexedObject> {
|
|
|
9
9
|
readonly dependencies: string[];
|
|
10
10
|
readonly parameterConfigurations: Record<keyof T, ParameterConfiguration>;
|
|
11
11
|
readonly configuration: ResourceConfiguration<T>;
|
|
12
|
+
readonly defaultValues: Partial<Record<keyof T, unknown>>;
|
|
12
13
|
protected constructor(configuration: ResourceConfiguration<T>);
|
|
13
14
|
onInitialize(): Promise<void>;
|
|
14
15
|
plan(desiredConfig: Partial<T> & ResourceConfig): Promise<Plan<T>>;
|
|
@@ -16,9 +17,11 @@ export declare abstract class Resource<T extends StringIndexedObject> {
|
|
|
16
17
|
private _applyCreate;
|
|
17
18
|
private _applyModify;
|
|
18
19
|
private _applyDestroy;
|
|
19
|
-
private
|
|
20
|
+
private initializeParameterConfigurations;
|
|
21
|
+
private initializeDefaultValues;
|
|
20
22
|
private validateResourceConfiguration;
|
|
21
23
|
private validateRefreshResults;
|
|
24
|
+
private addDefaultValues;
|
|
22
25
|
abstract validate(parameters: unknown): Promise<ValidationResult>;
|
|
23
26
|
abstract refresh(keys: Map<keyof T, T[keyof T]>): Promise<Partial<T> | null>;
|
|
24
27
|
abstract applyCreate(plan: Plan<T>): Promise<void>;
|
|
@@ -7,11 +7,13 @@ export class Resource {
|
|
|
7
7
|
dependencies;
|
|
8
8
|
parameterConfigurations;
|
|
9
9
|
configuration;
|
|
10
|
+
defaultValues;
|
|
10
11
|
constructor(configuration) {
|
|
11
12
|
this.validateResourceConfiguration(configuration);
|
|
12
13
|
this.typeId = configuration.type;
|
|
13
14
|
this.statefulParameters = new Map(configuration.statefulParameters?.map((sp) => [sp.name, sp]));
|
|
14
|
-
this.parameterConfigurations = this.
|
|
15
|
+
this.parameterConfigurations = this.initializeParameterConfigurations(configuration);
|
|
16
|
+
this.defaultValues = this.initializeDefaultValues(configuration);
|
|
15
17
|
this.dependencies = configuration.dependencies ?? [];
|
|
16
18
|
this.configuration = configuration;
|
|
17
19
|
}
|
|
@@ -22,6 +24,7 @@ export class Resource {
|
|
|
22
24
|
parameterConfigurations: this.parameterConfigurations,
|
|
23
25
|
};
|
|
24
26
|
const { resourceMetadata, parameters: desiredParameters } = splitUserConfig(desiredConfig);
|
|
27
|
+
this.addDefaultValues(desiredParameters);
|
|
25
28
|
const resourceParameters = Object.fromEntries([
|
|
26
29
|
...Object.entries(desiredParameters).filter(([key]) => !this.statefulParameters.has(key)),
|
|
27
30
|
]);
|
|
@@ -122,7 +125,7 @@ export class Resource {
|
|
|
122
125
|
}
|
|
123
126
|
await this.applyDestroy(plan);
|
|
124
127
|
}
|
|
125
|
-
|
|
128
|
+
initializeParameterConfigurations(resourceConfiguration) {
|
|
126
129
|
const resourceParameters = Object.fromEntries(Object.entries(resourceConfiguration.parameterConfigurations ?? {})
|
|
127
130
|
?.map(([name, value]) => ([name, { ...value, isStatefulParameter: false }])));
|
|
128
131
|
const statefulParameters = resourceConfiguration.statefulParameters
|
|
@@ -140,6 +143,14 @@ export class Resource {
|
|
|
140
143
|
...statefulParameters,
|
|
141
144
|
};
|
|
142
145
|
}
|
|
146
|
+
initializeDefaultValues(resourceConfiguration) {
|
|
147
|
+
if (!resourceConfiguration.parameterConfigurations) {
|
|
148
|
+
return {};
|
|
149
|
+
}
|
|
150
|
+
return Object.fromEntries(Object.entries(resourceConfiguration.parameterConfigurations)
|
|
151
|
+
.filter((p) => p[1]?.defaultValue !== undefined)
|
|
152
|
+
.map((config) => [config[0], config[1].defaultValue]));
|
|
153
|
+
}
|
|
143
154
|
validateResourceConfiguration(data) {
|
|
144
155
|
if (data.parameterConfigurations && data.statefulParameters) {
|
|
145
156
|
const parameters = [...Object.keys(data.parameterConfigurations)];
|
|
@@ -163,6 +174,14 @@ Missing: ${[...desiredKeys].filter((k) => !refreshKeys.has(k))};
|
|
|
163
174
|
Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`);
|
|
164
175
|
}
|
|
165
176
|
}
|
|
177
|
+
addDefaultValues(desired) {
|
|
178
|
+
Object.entries(this.defaultValues)
|
|
179
|
+
.forEach(([key, defaultValue]) => {
|
|
180
|
+
if (defaultValue !== undefined && desired[key] === undefined) {
|
|
181
|
+
desired[key] = defaultValue;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
166
185
|
async applyModify(parameterName, newValue, previousValue, allowDeletes, plan) { }
|
|
167
186
|
;
|
|
168
187
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Plan } from './plan.js';
|
|
3
|
+
import { TestResource } from './resource.test.js';
|
|
4
|
+
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
5
|
+
import { Resource } from './resource.js';
|
|
6
|
+
|
|
7
|
+
describe('Plan entity tests', () => {
|
|
8
|
+
it('Adds default values properly when plan is parsed from request (Create)', () => {
|
|
9
|
+
const resource = createResource();
|
|
10
|
+
|
|
11
|
+
const plan = Plan.fromResponse({
|
|
12
|
+
operation: ResourceOperation.CREATE,
|
|
13
|
+
resourceType: 'type',
|
|
14
|
+
parameters: [{
|
|
15
|
+
name: 'propB',
|
|
16
|
+
operation: ParameterOperation.ADD,
|
|
17
|
+
previousValue: null,
|
|
18
|
+
newValue: 'propBValue'
|
|
19
|
+
}]
|
|
20
|
+
}, resource.defaultValues);
|
|
21
|
+
|
|
22
|
+
expect(plan.currentConfig).toMatchObject({
|
|
23
|
+
type: 'type',
|
|
24
|
+
propA: null,
|
|
25
|
+
propB: null,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
expect(plan.desiredConfig).toMatchObject({
|
|
29
|
+
type: 'type',
|
|
30
|
+
propA: 'defaultA',
|
|
31
|
+
propB: 'propBValue',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
expect(plan.changeSet.parameterChanges
|
|
35
|
+
.every((pc) => pc.operation === ParameterOperation.ADD)
|
|
36
|
+
).to.be.true;
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('Adds default values properly when plan is parsed from request (Destroy)', () => {
|
|
40
|
+
const resource = createResource();
|
|
41
|
+
|
|
42
|
+
const plan = Plan.fromResponse({
|
|
43
|
+
operation: ResourceOperation.DESTROY,
|
|
44
|
+
resourceType: 'type',
|
|
45
|
+
parameters: [{
|
|
46
|
+
name: 'propB',
|
|
47
|
+
operation: ParameterOperation.REMOVE,
|
|
48
|
+
previousValue: 'propBValue',
|
|
49
|
+
newValue: null,
|
|
50
|
+
}]
|
|
51
|
+
}, resource.defaultValues);
|
|
52
|
+
|
|
53
|
+
expect(plan.currentConfig).toMatchObject({
|
|
54
|
+
type: 'type',
|
|
55
|
+
propA: 'defaultA',
|
|
56
|
+
propB: 'propBValue',
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
expect(plan.desiredConfig).toMatchObject({
|
|
60
|
+
type: 'type',
|
|
61
|
+
propA: null,
|
|
62
|
+
propB: null,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
expect(plan.changeSet.parameterChanges
|
|
66
|
+
.every((pc) => pc.operation === ParameterOperation.REMOVE)
|
|
67
|
+
).to.be.true;
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('Adds default values properly when plan is parsed from request (No-op)', () => {
|
|
71
|
+
const resource = createResource();
|
|
72
|
+
|
|
73
|
+
const plan = Plan.fromResponse({
|
|
74
|
+
operation: ResourceOperation.NOOP,
|
|
75
|
+
resourceType: 'type',
|
|
76
|
+
parameters: [{
|
|
77
|
+
name: 'propB',
|
|
78
|
+
operation: ParameterOperation.NOOP,
|
|
79
|
+
previousValue: 'propBValue',
|
|
80
|
+
newValue: 'propBValue',
|
|
81
|
+
}]
|
|
82
|
+
}, resource.defaultValues);
|
|
83
|
+
|
|
84
|
+
expect(plan.currentConfig).toMatchObject({
|
|
85
|
+
type: 'type',
|
|
86
|
+
propA: 'defaultA',
|
|
87
|
+
propB: 'propBValue',
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
expect(plan.desiredConfig).toMatchObject({
|
|
91
|
+
type: 'type',
|
|
92
|
+
propA: 'defaultA',
|
|
93
|
+
propB: 'propBValue',
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
expect(plan.changeSet.parameterChanges
|
|
97
|
+
.every((pc) => pc.operation === ParameterOperation.NOOP)
|
|
98
|
+
).to.be.true;
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('Does not add default value if a value has already been specified', () => {
|
|
102
|
+
const resource = createResource();
|
|
103
|
+
|
|
104
|
+
const plan = Plan.fromResponse({
|
|
105
|
+
operation: ResourceOperation.CREATE,
|
|
106
|
+
resourceType: 'type',
|
|
107
|
+
parameters: [{
|
|
108
|
+
name: 'propB',
|
|
109
|
+
operation: ParameterOperation.ADD,
|
|
110
|
+
previousValue: null,
|
|
111
|
+
newValue: 'propBValue',
|
|
112
|
+
}, {
|
|
113
|
+
name: 'propA',
|
|
114
|
+
operation: ParameterOperation.ADD,
|
|
115
|
+
previousValue: null,
|
|
116
|
+
newValue: 'propAValue',
|
|
117
|
+
}]
|
|
118
|
+
}, resource.defaultValues);
|
|
119
|
+
|
|
120
|
+
expect(plan.currentConfig).toMatchObject({
|
|
121
|
+
type: 'type',
|
|
122
|
+
propA: null,
|
|
123
|
+
propB: null,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
expect(plan.desiredConfig).toMatchObject({
|
|
127
|
+
type: 'type',
|
|
128
|
+
propA: 'propAValue',
|
|
129
|
+
propB: 'propBValue',
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
expect(plan.changeSet.parameterChanges
|
|
133
|
+
.every((pc) => pc.operation === ParameterOperation.ADD)
|
|
134
|
+
).to.be.true;
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
function createResource(): Resource<any> {
|
|
139
|
+
return new class extends TestResource {
|
|
140
|
+
constructor() {
|
|
141
|
+
super({
|
|
142
|
+
type: 'type',
|
|
143
|
+
parameterConfigurations: {
|
|
144
|
+
propA: {
|
|
145
|
+
defaultValue: 'defaultA'
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/entities/plan.ts
CHANGED
|
@@ -75,29 +75,70 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
75
75
|
return this.resourceMetadata.type
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan']): Plan<T> {
|
|
78
|
+
static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues: Partial<Record<keyof T, unknown>>): Plan<T> {
|
|
79
79
|
if (!data) {
|
|
80
80
|
throw new Error('Data is empty');
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
addDefaultValues();
|
|
84
|
+
|
|
83
85
|
return new Plan(
|
|
84
86
|
randomUUID(),
|
|
85
87
|
new ChangeSet<T>(
|
|
86
88
|
data.operation,
|
|
87
|
-
data.parameters
|
|
88
|
-
...value,
|
|
89
|
-
previousValue: null,
|
|
90
|
-
})),
|
|
89
|
+
data.parameters
|
|
91
90
|
),
|
|
92
91
|
{
|
|
93
92
|
type: data.resourceType,
|
|
94
93
|
name: data.resourceName,
|
|
95
|
-
...(data.parameters.reduce(
|
|
96
|
-
(prev, { name, newValue }) => Object.assign(prev, { [name]: newValue }),
|
|
97
|
-
{}
|
|
98
|
-
))
|
|
99
94
|
},
|
|
100
95
|
);
|
|
96
|
+
|
|
97
|
+
function addDefaultValues(): void {
|
|
98
|
+
Object.entries(defaultValues)
|
|
99
|
+
.forEach(([key, defaultValue]) => {
|
|
100
|
+
const configValueExists = data
|
|
101
|
+
?.parameters
|
|
102
|
+
.find((p) => p.name === key) !== undefined;
|
|
103
|
+
|
|
104
|
+
if (!configValueExists) {
|
|
105
|
+
switch (data?.operation) {
|
|
106
|
+
case ResourceOperation.CREATE: {
|
|
107
|
+
data?.parameters.push({
|
|
108
|
+
name: key,
|
|
109
|
+
operation: ParameterOperation.ADD,
|
|
110
|
+
previousValue: null,
|
|
111
|
+
newValue: defaultValue,
|
|
112
|
+
});
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
case ResourceOperation.DESTROY: {
|
|
117
|
+
data?.parameters.push({
|
|
118
|
+
name: key,
|
|
119
|
+
operation: ParameterOperation.REMOVE,
|
|
120
|
+
previousValue: defaultValue,
|
|
121
|
+
newValue: null,
|
|
122
|
+
});
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
case ResourceOperation.MODIFY:
|
|
127
|
+
case ResourceOperation.RECREATE:
|
|
128
|
+
case ResourceOperation.NOOP: {
|
|
129
|
+
data?.parameters.push({
|
|
130
|
+
name: key,
|
|
131
|
+
operation: ParameterOperation.NOOP,
|
|
132
|
+
previousValue: defaultValue,
|
|
133
|
+
newValue: defaultValue,
|
|
134
|
+
});
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
101
142
|
}
|
|
102
143
|
|
|
103
144
|
get desiredConfig(): T {
|
package/src/entities/plugin.ts
CHANGED
|
@@ -94,7 +94,12 @@ export class Plugin {
|
|
|
94
94
|
return this.planStorage.get(planId)!
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
if (!planRequest?.resourceType || !this.resources.has(planRequest.resourceType)) {
|
|
98
|
+
throw new Error('Malformed plan. Resource type must be supplied');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const resource = this.resources.get(planRequest.resourceType);
|
|
102
|
+
return Plan.fromResponse(data.plan, resource?.defaultValues!);
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
protected async crossValidateResources(configs: ResourceConfig[]): Promise<void> {}
|
|
@@ -17,6 +17,10 @@ export interface ResourceParameterConfiguration {
|
|
|
17
17
|
* @param current
|
|
18
18
|
*/
|
|
19
19
|
isEqual?: (desired: any, current: any) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Default value for the parameter. If a value is not provided in the config, the library will use this value.
|
|
22
|
+
*/
|
|
23
|
+
defaultValue?: unknown,
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
/**
|
|
@@ -293,4 +293,61 @@ describe('Resource tests', () => {
|
|
|
293
293
|
}).to.not.throw;
|
|
294
294
|
})
|
|
295
295
|
|
|
296
|
+
it('Allows default values to be added', async () => {
|
|
297
|
+
const resource = new class extends TestResource {
|
|
298
|
+
constructor() {
|
|
299
|
+
super({
|
|
300
|
+
type: 'type',
|
|
301
|
+
parameterConfigurations: {
|
|
302
|
+
propA: { defaultValue: 'propADefault' }
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// @ts-ignore
|
|
308
|
+
async refresh(desired: Map<string, unknown>): Promise<Partial<TestConfig>> {
|
|
309
|
+
expect(desired.has('propA')).to.be.true;
|
|
310
|
+
expect(desired.get('propA')).to.be.eq('propADefault');
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
propA: 'propAAfter'
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const plan = await resource.plan({ type: 'resource'})
|
|
319
|
+
expect(plan.currentConfig.propA).to.eq('propAAfter');
|
|
320
|
+
expect(plan.desiredConfig.propA).to.eq('propADefault');
|
|
321
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.RECREATE);
|
|
322
|
+
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('Allows default values to be added (ignore default value if already present)', async () => {
|
|
326
|
+
const resource = new class extends TestResource {
|
|
327
|
+
constructor() {
|
|
328
|
+
super({
|
|
329
|
+
type: 'type',
|
|
330
|
+
parameterConfigurations: {
|
|
331
|
+
propA: { defaultValue: 'propADefault' }
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// @ts-ignore
|
|
337
|
+
async refresh(desired: Map<string, unknown>): Promise<Partial<TestConfig>> {
|
|
338
|
+
expect(desired.has('propA')).to.be.true;
|
|
339
|
+
expect(desired.get('propA')).to.be.eq('propA');
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
propA: 'propAAfter'
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const plan = await resource.plan({ type: 'resource', propA: 'propA'})
|
|
348
|
+
expect(plan.currentConfig.propA).to.eq('propAAfter');
|
|
349
|
+
expect(plan.desiredConfig.propA).to.eq('propA');
|
|
350
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.RECREATE);
|
|
351
|
+
|
|
352
|
+
})
|
|
296
353
|
});
|
package/src/entities/resource.ts
CHANGED
|
@@ -20,13 +20,15 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
20
20
|
readonly dependencies: string[]; // TODO: Change this to a string
|
|
21
21
|
readonly parameterConfigurations: Record<keyof T, ParameterConfiguration>
|
|
22
22
|
readonly configuration: ResourceConfiguration<T>;
|
|
23
|
+
readonly defaultValues: Partial<Record<keyof T, unknown>>;
|
|
23
24
|
|
|
24
25
|
protected constructor(configuration: ResourceConfiguration<T>) {
|
|
25
26
|
this.validateResourceConfiguration(configuration);
|
|
26
27
|
|
|
27
28
|
this.typeId = configuration.type;
|
|
28
29
|
this.statefulParameters = new Map(configuration.statefulParameters?.map((sp) => [sp.name, sp]));
|
|
29
|
-
this.parameterConfigurations = this.
|
|
30
|
+
this.parameterConfigurations = this.initializeParameterConfigurations(configuration);
|
|
31
|
+
this.defaultValues = this.initializeDefaultValues(configuration);
|
|
30
32
|
|
|
31
33
|
this.dependencies = configuration.dependencies ?? [];
|
|
32
34
|
this.configuration = configuration;
|
|
@@ -47,6 +49,8 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
47
49
|
|
|
48
50
|
const { resourceMetadata, parameters: desiredParameters } = splitUserConfig(desiredConfig);
|
|
49
51
|
|
|
52
|
+
this.addDefaultValues(desiredParameters);
|
|
53
|
+
|
|
50
54
|
const resourceParameters = Object.fromEntries([
|
|
51
55
|
...Object.entries(desiredParameters).filter(([key]) => !this.statefulParameters.has(key)),
|
|
52
56
|
]) as Partial<T>;
|
|
@@ -183,7 +187,7 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
183
187
|
await this.applyDestroy(plan);
|
|
184
188
|
}
|
|
185
189
|
|
|
186
|
-
private
|
|
190
|
+
private initializeParameterConfigurations(
|
|
187
191
|
resourceConfiguration: ResourceConfiguration<T>
|
|
188
192
|
): Record<keyof T, ParameterConfiguration> {
|
|
189
193
|
const resourceParameters = Object.fromEntries(
|
|
@@ -209,6 +213,20 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
209
213
|
|
|
210
214
|
}
|
|
211
215
|
|
|
216
|
+
private initializeDefaultValues(
|
|
217
|
+
resourceConfiguration: ResourceConfiguration<T>
|
|
218
|
+
): Partial<Record<keyof T, unknown>> {
|
|
219
|
+
if (!resourceConfiguration.parameterConfigurations) {
|
|
220
|
+
return {};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return Object.fromEntries(
|
|
224
|
+
Object.entries(resourceConfiguration.parameterConfigurations)
|
|
225
|
+
.filter((p) => p[1]?.defaultValue !== undefined)
|
|
226
|
+
.map((config) => [config[0], config[1]!.defaultValue])
|
|
227
|
+
) as Partial<Record<keyof T, unknown>>;
|
|
228
|
+
}
|
|
229
|
+
|
|
212
230
|
private validateResourceConfiguration(data: ResourceConfiguration<T>) {
|
|
213
231
|
// Stateful parameters are configured within the object not in the resource.
|
|
214
232
|
if (data.parameterConfigurations && data.statefulParameters) {
|
|
@@ -220,8 +238,6 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
220
238
|
throw new Error(`Resource ${this.typeId} cannot declare a parameter as both stateful and non-stateful`);
|
|
221
239
|
}
|
|
222
240
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
241
|
}
|
|
226
242
|
|
|
227
243
|
private validateRefreshResults(refresh: Partial<T> | null, desiredMap: Map<keyof T, T[keyof T]>) {
|
|
@@ -242,6 +258,16 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
|
242
258
|
}
|
|
243
259
|
}
|
|
244
260
|
|
|
261
|
+
private addDefaultValues(desired: Partial<T>): void {
|
|
262
|
+
Object.entries(this.defaultValues)
|
|
263
|
+
.forEach(([key, defaultValue]) => {
|
|
264
|
+
if (defaultValue !== undefined && desired[key as any] === undefined) {
|
|
265
|
+
// @ts-ignore
|
|
266
|
+
desired[key] = defaultValue;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
245
271
|
abstract validate(parameters: unknown): Promise<ValidationResult>;
|
|
246
272
|
|
|
247
273
|
abstract refresh(keys: Map<keyof T, T[keyof T]>): Promise<Partial<T> | null>;
|