codify-plugin-lib 1.0.64 → 1.0.66
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/change-set.js +4 -4
- package/dist/entities/errors.d.ts +7 -0
- package/dist/entities/errors.js +9 -0
- package/dist/entities/plan-types.d.ts +1 -1
- package/dist/entities/plan.d.ts +3 -3
- package/dist/entities/plan.js +9 -3
- package/dist/entities/plugin.d.ts +2 -1
- package/dist/entities/plugin.js +10 -0
- package/dist/entities/resource-types.d.ts +1 -1
- package/dist/entities/resource.d.ts +3 -2
- package/dist/entities/resource.js +63 -27
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/messages/handlers.js +16 -2
- package/package.json +1 -1
- package/src/entities/change-set.ts +5 -4
- package/src/entities/errors.ts +17 -0
- package/src/entities/plan-types.ts +1 -1
- package/src/entities/plan.test.ts +3 -15
- package/src/entities/plan.ts +14 -6
- package/src/entities/plugin.test.ts +193 -0
- package/src/entities/plugin.ts +22 -1
- package/src/entities/resource-parameters.test.ts +14 -26
- package/src/entities/resource-stateful-mode.test.ts +247 -0
- package/src/entities/resource-types.ts +1 -1
- package/src/entities/resource.test.ts +12 -24
- package/src/entities/resource.ts +98 -41
- package/src/entities/stateful-parameter.test.ts +2 -5
- package/src/index.ts +0 -1
- package/src/messages/handlers.ts +17 -2
- package/src/utils/test-utils.test.ts +0 -52
- package/src/utils/test-utils.ts +0 -20
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Plugin } from './plugin.js';
|
|
3
|
+
import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
|
|
4
|
+
import { Resource } from './resource.js';
|
|
5
|
+
import { Plan } from './plan.js';
|
|
6
|
+
import { ValidationResult } from './resource-types.js';
|
|
7
|
+
import { ApplyValidationError } from './errors.js';
|
|
8
|
+
|
|
9
|
+
interface TestConfig extends StringIndexedObject {
|
|
10
|
+
propA: string;
|
|
11
|
+
propB: number;
|
|
12
|
+
propC?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class TestResource extends Resource<TestConfig> {
|
|
16
|
+
constructor() {
|
|
17
|
+
super({
|
|
18
|
+
type: 'testResource'
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
applyCreate(plan: Plan<TestConfig>): Promise<void> {
|
|
23
|
+
return Promise.resolve(undefined);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
applyDestroy(plan: Plan<TestConfig>): Promise<void> {
|
|
27
|
+
return Promise.resolve(undefined);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
|
|
31
|
+
return {
|
|
32
|
+
propA: 'a',
|
|
33
|
+
propB: 10,
|
|
34
|
+
propC: 'c',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async validateResource(config: unknown): Promise<ValidationResult> {
|
|
39
|
+
return {
|
|
40
|
+
isValid: true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe('Plugin tests', () => {
|
|
46
|
+
it('Validates that applies were successfully applied', async () => {
|
|
47
|
+
const resource= new class extends TestResource {
|
|
48
|
+
async applyCreate(plan: Plan<TestConfig>): Promise<void> {
|
|
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])
|
|
60
|
+
|
|
61
|
+
const plan = {
|
|
62
|
+
operation: ResourceOperation.CREATE,
|
|
63
|
+
resourceType: 'testResource',
|
|
64
|
+
parameters: [
|
|
65
|
+
{ name: 'propA', operation: ParameterOperation.ADD, newValue: 'abc', previousValue: null },
|
|
66
|
+
]
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// If this doesn't throw then it passes the test
|
|
70
|
+
await plugin.apply({ plan });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('Validates that applies were successfully applied (error)', async () => {
|
|
74
|
+
const resource = new class extends TestResource {
|
|
75
|
+
async applyCreate(plan: Plan<TestConfig>): Promise<void> {
|
|
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])
|
|
108
|
+
|
|
109
|
+
const plan = {
|
|
110
|
+
operation: ResourceOperation.DESTROY,
|
|
111
|
+
resourceType: 'testResource',
|
|
112
|
+
parameters: [
|
|
113
|
+
{ name: 'propA', operation: ParameterOperation.REMOVE, newValue: null, previousValue: 'abc' },
|
|
114
|
+
]
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// If this doesn't throw then it passes the test
|
|
118
|
+
await testPlugin.apply({ plan })
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('Validates that deletes were successfully applied (error)', async () => {
|
|
122
|
+
const resource = new class extends TestResource {
|
|
123
|
+
async applyCreate(plan: Plan<TestConfig>): Promise<void> {
|
|
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])
|
|
157
|
+
|
|
158
|
+
const plan = {
|
|
159
|
+
operation: ResourceOperation.RECREATE,
|
|
160
|
+
resourceType: 'testResource',
|
|
161
|
+
parameters: [
|
|
162
|
+
{ name: 'propA', operation: ParameterOperation.MODIFY, newValue: 'def', previousValue: 'abc' },
|
|
163
|
+
]
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// If this doesn't throw then it passes the test
|
|
167
|
+
await testPlugin.apply({ plan })
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('Validates that modify was successfully applied (error)', async () => {
|
|
171
|
+
const resource = new class extends TestResource {
|
|
172
|
+
async applyCreate(plan: Plan<TestConfig>): Promise<void> {
|
|
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])
|
|
181
|
+
|
|
182
|
+
const plan = {
|
|
183
|
+
operation: ResourceOperation.DESTROY,
|
|
184
|
+
resourceType: 'testResource',
|
|
185
|
+
parameters: [
|
|
186
|
+
{ name: 'propA', operation: ParameterOperation.REMOVE, newValue: 'def', previousValue: 'abc' },
|
|
187
|
+
]
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// If this doesn't throw then it passes the test
|
|
191
|
+
expect(async () => await testPlugin.apply({ plan })).rejects.toThrowError(expect.any(ApplyValidationError));
|
|
192
|
+
});
|
|
193
|
+
});
|
package/src/entities/plugin.ts
CHANGED
|
@@ -5,14 +5,24 @@ import {
|
|
|
5
5
|
PlanRequestData,
|
|
6
6
|
PlanResponseData,
|
|
7
7
|
ResourceConfig,
|
|
8
|
+
ResourceOperation,
|
|
8
9
|
ValidateRequestData,
|
|
9
10
|
ValidateResponseData
|
|
10
11
|
} from 'codify-schemas';
|
|
11
12
|
import { Plan } from './plan.js';
|
|
12
13
|
import { splitUserConfig } from '../utils/utils.js';
|
|
14
|
+
import { ApplyValidationError } from './errors.js';
|
|
13
15
|
|
|
14
16
|
export class Plugin {
|
|
15
|
-
planStorage: Map<string, Plan<
|
|
17
|
+
planStorage: Map<string, Plan<any>>;
|
|
18
|
+
|
|
19
|
+
static create(name: string, resources: Resource<any>[]) {
|
|
20
|
+
const resourceMap = new Map<string, Resource<any>>(
|
|
21
|
+
resources.map((r) => [r.typeId, r] as const)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return new Plugin(name, resourceMap);
|
|
25
|
+
}
|
|
16
26
|
|
|
17
27
|
constructor(
|
|
18
28
|
public name: string,
|
|
@@ -82,6 +92,17 @@ export class Plugin {
|
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
await resource.apply(plan);
|
|
95
|
+
|
|
96
|
+
// Perform a validation check after to ensure that the plan was properly applied.
|
|
97
|
+
// Sometimes no errors are returned (exit code 0) but the apply was not successful
|
|
98
|
+
const validationPlan = await resource.plan(
|
|
99
|
+
plan.desiredConfig,
|
|
100
|
+
plan.currentConfig,
|
|
101
|
+
true,
|
|
102
|
+
);
|
|
103
|
+
if (validationPlan.changeSet.operation !== ResourceOperation.NOOP) {
|
|
104
|
+
throw new ApplyValidationError(plan, validationPlan);
|
|
105
|
+
}
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
private resolvePlan(data: ApplyRequestData): Plan<ResourceConfig> {
|
|
@@ -6,7 +6,7 @@ import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
|
6
6
|
import { TestConfig, TestResource } from './resource.test.js';
|
|
7
7
|
import { TransformParameter } from './transform-parameter.js';
|
|
8
8
|
|
|
9
|
-
class TestParameter extends StatefulParameter<TestConfig, string> {
|
|
9
|
+
export class TestParameter extends StatefulParameter<TestConfig, string> {
|
|
10
10
|
constructor(options?: StatefulParameterOptions<string>) {
|
|
11
11
|
super(options ?? {})
|
|
12
12
|
}
|
|
@@ -55,11 +55,7 @@ describe('Resource parameter tests', () => {
|
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
expect(statefulParameter.refresh.notCalled).to.be.true;
|
|
58
|
-
expect(plan.currentConfig).
|
|
59
|
-
type: 'resource',
|
|
60
|
-
propA: null,
|
|
61
|
-
propB: null,
|
|
62
|
-
})
|
|
58
|
+
expect(plan.currentConfig).to.be.null;
|
|
63
59
|
expect(plan.desiredConfig).toMatchObject({
|
|
64
60
|
type: 'resource',
|
|
65
61
|
propA: 'a',
|
|
@@ -119,7 +115,7 @@ describe('Resource parameter tests', () => {
|
|
|
119
115
|
type: 'resource',
|
|
120
116
|
parameterOptions: {
|
|
121
117
|
propA: { statefulParameter: statefulParameterSpy },
|
|
122
|
-
propB: {
|
|
118
|
+
propB: { modifyOnChange: true },
|
|
123
119
|
}
|
|
124
120
|
});
|
|
125
121
|
}
|
|
@@ -171,10 +167,7 @@ describe('Resource parameter tests', () => {
|
|
|
171
167
|
})
|
|
172
168
|
|
|
173
169
|
expect(statefulParameter.refresh.notCalled).to.be.true;
|
|
174
|
-
expect(plan.currentConfig).
|
|
175
|
-
type: 'resource',
|
|
176
|
-
propA: null,
|
|
177
|
-
})
|
|
170
|
+
expect(plan.currentConfig).to.be.null;
|
|
178
171
|
expect(plan.desiredConfig).toMatchObject({
|
|
179
172
|
type: 'resource',
|
|
180
173
|
propA: 'abc',
|
|
@@ -182,7 +175,6 @@ describe('Resource parameter tests', () => {
|
|
|
182
175
|
expect(plan.changeSet.operation).to.eq(ResourceOperation.CREATE);
|
|
183
176
|
})
|
|
184
177
|
|
|
185
|
-
|
|
186
178
|
it('Filters array results in stateless mode to prevent modify from being called', async () => {
|
|
187
179
|
const statefulParameter = new class extends TestParameter {
|
|
188
180
|
async refresh(): Promise<any | null> {
|
|
@@ -350,10 +342,10 @@ describe('Resource parameter tests', () => {
|
|
|
350
342
|
propE: 'propE',
|
|
351
343
|
});
|
|
352
344
|
|
|
353
|
-
expect(plan.currentConfig
|
|
354
|
-
expect(plan.currentConfig
|
|
355
|
-
expect(plan.currentConfig
|
|
356
|
-
expect(plan.currentConfig
|
|
345
|
+
expect(plan.currentConfig?.propB).to.be.lessThan(plan.currentConfig?.propC as any);
|
|
346
|
+
expect(plan.currentConfig?.propC).to.be.lessThan(plan.currentConfig?.propA as any);
|
|
347
|
+
expect(plan.currentConfig?.propA).to.be.lessThan(plan.currentConfig?.propD as any);
|
|
348
|
+
expect(plan.currentConfig?.propD).to.be.lessThan(plan.currentConfig?.propE as any);
|
|
357
349
|
})
|
|
358
350
|
|
|
359
351
|
it('Applies stateful parameters in the order specified', async () => {
|
|
@@ -483,9 +475,9 @@ describe('Resource parameter tests', () => {
|
|
|
483
475
|
expect(resource.refresh.getCall(0).firstArg.has('propB')).to.be.true;
|
|
484
476
|
expect(resource.refresh.getCall(0).firstArg.has('propC')).to.be.false;
|
|
485
477
|
|
|
486
|
-
expect(plan.desiredConfig
|
|
487
|
-
expect(plan.desiredConfig
|
|
488
|
-
expect(plan.desiredConfig
|
|
478
|
+
expect(plan.desiredConfig?.propA).to.eq('propA');
|
|
479
|
+
expect(plan.desiredConfig?.propB).to.eq(10);
|
|
480
|
+
expect(plan.desiredConfig?.propC).to.be.undefined;
|
|
489
481
|
|
|
490
482
|
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
491
483
|
})
|
|
@@ -570,8 +562,8 @@ describe('Resource parameter tests', () => {
|
|
|
570
562
|
propC: 'propC',
|
|
571
563
|
});
|
|
572
564
|
|
|
573
|
-
expect(plan.desiredConfig
|
|
574
|
-
expect(plan.desiredConfig
|
|
565
|
+
expect(plan.desiredConfig?.propE).to.be.lessThan(plan.desiredConfig?.propF as any);
|
|
566
|
+
expect(plan.desiredConfig?.propF).to.be.lessThan(plan.desiredConfig?.propD as any);
|
|
575
567
|
})
|
|
576
568
|
|
|
577
569
|
it('Plans transform even for creating new resources', async () => {
|
|
@@ -602,11 +594,7 @@ describe('Resource parameter tests', () => {
|
|
|
602
594
|
propB: 10,
|
|
603
595
|
propC: 'propC',
|
|
604
596
|
});
|
|
605
|
-
expect(plan.currentConfig).
|
|
606
|
-
type: 'resourceType',
|
|
607
|
-
propD: null,
|
|
608
|
-
propE: null,
|
|
609
|
-
})
|
|
597
|
+
expect(plan.currentConfig).to.be.null;
|
|
610
598
|
expect(plan.desiredConfig).toMatchObject({
|
|
611
599
|
type: 'resourceType',
|
|
612
600
|
propD: 'abc',
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { TestConfig, TestResource } from './resource.test.js';
|
|
3
|
+
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
4
|
+
import { TestParameter } from './resource-parameters.test.js';
|
|
5
|
+
import { StatefulParameter } from './stateful-parameter.js';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
describe('Resource tests for stateful plans', () => {
|
|
9
|
+
it('Supports delete operations ', async () => {
|
|
10
|
+
const resource = new class extends TestResource {
|
|
11
|
+
constructor() {
|
|
12
|
+
super({ type: 'resource' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
|
|
16
|
+
return {
|
|
17
|
+
propA: 'propADifferent',
|
|
18
|
+
propB: undefined,
|
|
19
|
+
propC: 'propCDifferent',
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const plan = await resource.plan(
|
|
25
|
+
null,
|
|
26
|
+
{
|
|
27
|
+
type: 'resource',
|
|
28
|
+
propA: 'propA',
|
|
29
|
+
propB: 10,
|
|
30
|
+
propC: 'propC',
|
|
31
|
+
}, true
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(plan).toMatchObject({
|
|
35
|
+
changeSet: {
|
|
36
|
+
operation: ResourceOperation.DESTROY,
|
|
37
|
+
parameterChanges: [
|
|
38
|
+
{
|
|
39
|
+
name: "propA",
|
|
40
|
+
previousValue: "propADifferent",
|
|
41
|
+
newValue: null,
|
|
42
|
+
operation: ParameterOperation.REMOVE
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "propC",
|
|
46
|
+
previousValue: "propCDifferent",
|
|
47
|
+
newValue: null,
|
|
48
|
+
operation: ParameterOperation.REMOVE
|
|
49
|
+
},
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
resourceMetadata: {
|
|
53
|
+
type: 'resource'
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('Supports create operations', async () => {
|
|
59
|
+
const resource = new class extends TestResource {
|
|
60
|
+
constructor() {
|
|
61
|
+
super({ type: 'resource' });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const plan = await resource.plan(
|
|
70
|
+
{
|
|
71
|
+
type: 'resource',
|
|
72
|
+
propA: 'propA',
|
|
73
|
+
propB: 10,
|
|
74
|
+
propC: 'propC',
|
|
75
|
+
},
|
|
76
|
+
null,
|
|
77
|
+
true
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(plan).toMatchObject({
|
|
81
|
+
changeSet: {
|
|
82
|
+
operation: ResourceOperation.CREATE,
|
|
83
|
+
parameterChanges: [
|
|
84
|
+
{
|
|
85
|
+
name: "propA",
|
|
86
|
+
newValue: "propA",
|
|
87
|
+
previousValue: null,
|
|
88
|
+
operation: ParameterOperation.ADD
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "propB",
|
|
92
|
+
newValue: 10,
|
|
93
|
+
previousValue: null,
|
|
94
|
+
operation: ParameterOperation.ADD
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "propC",
|
|
98
|
+
newValue: 'propC',
|
|
99
|
+
previousValue: null,
|
|
100
|
+
operation: ParameterOperation.ADD
|
|
101
|
+
},
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
resourceMetadata: {
|
|
105
|
+
type: 'resource'
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('Supports re-create operations', async () => {
|
|
111
|
+
const resource = new class extends TestResource {
|
|
112
|
+
constructor() {
|
|
113
|
+
super({ type: 'resource' });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
|
|
117
|
+
return {
|
|
118
|
+
propA: 'propA',
|
|
119
|
+
propC: 'propC',
|
|
120
|
+
propB: undefined
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const plan = await resource.plan(
|
|
126
|
+
{
|
|
127
|
+
type: 'resource',
|
|
128
|
+
propA: 'propA',
|
|
129
|
+
propB: 10,
|
|
130
|
+
propC: 'propC',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: 'resource',
|
|
134
|
+
propA: 'propA',
|
|
135
|
+
propC: 'propC'
|
|
136
|
+
},
|
|
137
|
+
true
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(plan).toMatchObject({
|
|
141
|
+
changeSet: {
|
|
142
|
+
operation: ResourceOperation.RECREATE,
|
|
143
|
+
parameterChanges: expect.arrayContaining([
|
|
144
|
+
{
|
|
145
|
+
name: "propA",
|
|
146
|
+
newValue: "propA",
|
|
147
|
+
previousValue: "propA",
|
|
148
|
+
operation: ParameterOperation.NOOP
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "propB",
|
|
152
|
+
newValue: 10,
|
|
153
|
+
previousValue: null,
|
|
154
|
+
operation: ParameterOperation.ADD
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "propC",
|
|
158
|
+
newValue: 'propC',
|
|
159
|
+
previousValue: 'propC',
|
|
160
|
+
operation: ParameterOperation.NOOP
|
|
161
|
+
},
|
|
162
|
+
])
|
|
163
|
+
},
|
|
164
|
+
resourceMetadata: {
|
|
165
|
+
type: 'resource'
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('Supports stateful parameters', async () => {
|
|
171
|
+
const statefulParameter = new class extends TestParameter {
|
|
172
|
+
async refresh(): Promise<string | null> {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const resource = new class extends TestResource {
|
|
178
|
+
constructor() {
|
|
179
|
+
super({
|
|
180
|
+
type: 'resource',
|
|
181
|
+
parameterOptions: {
|
|
182
|
+
propD: { statefulParameter: statefulParameter as StatefulParameter<TestConfig, string> },
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
|
|
188
|
+
return {
|
|
189
|
+
propA: 'propA',
|
|
190
|
+
propC: 'propC',
|
|
191
|
+
propB: undefined
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const plan = await resource.plan(
|
|
197
|
+
{
|
|
198
|
+
type: 'resource',
|
|
199
|
+
propA: 'propA',
|
|
200
|
+
propB: 10,
|
|
201
|
+
propC: 'propC',
|
|
202
|
+
propD: 'propD'
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
type: 'resource',
|
|
206
|
+
propA: 'propA',
|
|
207
|
+
propC: 'propC'
|
|
208
|
+
},
|
|
209
|
+
true
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
expect(plan).toMatchObject({
|
|
213
|
+
changeSet: {
|
|
214
|
+
operation: ResourceOperation.RECREATE,
|
|
215
|
+
parameterChanges: expect.arrayContaining([
|
|
216
|
+
{
|
|
217
|
+
name: "propA",
|
|
218
|
+
newValue: "propA",
|
|
219
|
+
previousValue: "propA",
|
|
220
|
+
operation: ParameterOperation.NOOP
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: "propB",
|
|
224
|
+
newValue: 10,
|
|
225
|
+
previousValue: null,
|
|
226
|
+
operation: ParameterOperation.ADD
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "propC",
|
|
230
|
+
newValue: 'propC',
|
|
231
|
+
previousValue: 'propC',
|
|
232
|
+
operation: ParameterOperation.NOOP
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: "propD",
|
|
236
|
+
newValue: 'propD',
|
|
237
|
+
previousValue: null,
|
|
238
|
+
operation: ParameterOperation.ADD
|
|
239
|
+
},
|
|
240
|
+
])
|
|
241
|
+
},
|
|
242
|
+
resourceMetadata: {
|
|
243
|
+
type: 'resource'
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
})
|
|
@@ -7,7 +7,7 @@ export interface ResourceParameterOptions {
|
|
|
7
7
|
/**
|
|
8
8
|
* Chose if the resource should be re-created or modified if this parameter is changed. Defaults to false (re-create).
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
modifyOnChange?: boolean;
|
|
11
11
|
/**
|
|
12
12
|
* Customize the equality comparison for a parameter.
|
|
13
13
|
* @param desired
|
|
@@ -195,8 +195,8 @@ describe('Resource tests', () => {
|
|
|
195
195
|
super({
|
|
196
196
|
type: 'resource',
|
|
197
197
|
parameterOptions: {
|
|
198
|
-
propA: {
|
|
199
|
-
propB: {
|
|
198
|
+
propA: { modifyOnChange: true },
|
|
199
|
+
propB: { modifyOnChange: true },
|
|
200
200
|
}
|
|
201
201
|
});
|
|
202
202
|
}
|
|
@@ -218,12 +218,6 @@ describe('Resource tests', () => {
|
|
|
218
218
|
|
|
219
219
|
it('Validates the resource options correct (pass)', () => {
|
|
220
220
|
const statefulParameter = new class extends StatefulParameter<TestConfig, string> {
|
|
221
|
-
constructor() {
|
|
222
|
-
super({
|
|
223
|
-
name: 'propC',
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
221
|
async refresh(): Promise<string | null> {
|
|
228
222
|
return null;
|
|
229
223
|
}
|
|
@@ -244,7 +238,7 @@ describe('Resource tests', () => {
|
|
|
244
238
|
type: 'type',
|
|
245
239
|
dependencies: ['homebrew', 'python'],
|
|
246
240
|
parameterOptions: {
|
|
247
|
-
propA: {
|
|
241
|
+
propA: { modifyOnChange: true },
|
|
248
242
|
propB: { statefulParameter },
|
|
249
243
|
propC: { isEqual: (a, b) => true },
|
|
250
244
|
}
|
|
@@ -255,12 +249,6 @@ describe('Resource tests', () => {
|
|
|
255
249
|
|
|
256
250
|
it('Validates the resource options correct (fail)', () => {
|
|
257
251
|
const statefulParameter = new class extends StatefulParameter<TestConfig, string> {
|
|
258
|
-
constructor() {
|
|
259
|
-
super({
|
|
260
|
-
name: 'propC',
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
252
|
async refresh(): Promise<string | null> {
|
|
265
253
|
return null;
|
|
266
254
|
}
|
|
@@ -281,7 +269,7 @@ describe('Resource tests', () => {
|
|
|
281
269
|
type: 'type',
|
|
282
270
|
dependencies: ['homebrew', 'python'],
|
|
283
271
|
parameterOptions: {
|
|
284
|
-
propA: {
|
|
272
|
+
propA: { modifyOnChange: true },
|
|
285
273
|
propB: { statefulParameter },
|
|
286
274
|
propC: { isEqual: (a, b) => true },
|
|
287
275
|
}
|
|
@@ -313,8 +301,8 @@ describe('Resource tests', () => {
|
|
|
313
301
|
}
|
|
314
302
|
|
|
315
303
|
const plan = await resource.plan({ type: 'resource'})
|
|
316
|
-
expect(plan.currentConfig
|
|
317
|
-
expect(plan.desiredConfig
|
|
304
|
+
expect(plan.currentConfig?.propA).to.eq('propAAfter');
|
|
305
|
+
expect(plan.desiredConfig?.propA).to.eq('propADefault');
|
|
318
306
|
expect(plan.changeSet.operation).to.eq(ResourceOperation.RECREATE);
|
|
319
307
|
})
|
|
320
308
|
|
|
@@ -339,8 +327,8 @@ describe('Resource tests', () => {
|
|
|
339
327
|
}
|
|
340
328
|
|
|
341
329
|
const plan = await resource.plan({ type: 'resource'})
|
|
342
|
-
expect(plan.currentConfig
|
|
343
|
-
expect(plan.desiredConfig
|
|
330
|
+
expect(plan.currentConfig?.propE).to.eq('propEDefault');
|
|
331
|
+
expect(plan.desiredConfig?.propE).to.eq('propEDefault');
|
|
344
332
|
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
345
333
|
})
|
|
346
334
|
|
|
@@ -361,8 +349,8 @@ describe('Resource tests', () => {
|
|
|
361
349
|
}
|
|
362
350
|
|
|
363
351
|
const plan = await resource.plan({ type: 'resource'})
|
|
364
|
-
expect(plan.currentConfig
|
|
365
|
-
expect(plan.desiredConfig
|
|
352
|
+
expect(plan.currentConfig).to.be.null
|
|
353
|
+
expect(plan.desiredConfig!.propE).to.eq('propEDefault');
|
|
366
354
|
expect(plan.changeSet.operation).to.eq(ResourceOperation.CREATE);
|
|
367
355
|
})
|
|
368
356
|
|
|
@@ -389,8 +377,8 @@ describe('Resource tests', () => {
|
|
|
389
377
|
}
|
|
390
378
|
|
|
391
379
|
const plan = await resource.plan({ type: 'resource', propA: 'propA'})
|
|
392
|
-
expect(plan.currentConfig
|
|
393
|
-
expect(plan.desiredConfig
|
|
380
|
+
expect(plan.currentConfig?.propA).to.eq('propAAfter');
|
|
381
|
+
expect(plan.desiredConfig?.propA).to.eq('propA');
|
|
394
382
|
expect(plan.changeSet.operation).to.eq(ResourceOperation.RECREATE);
|
|
395
383
|
});
|
|
396
384
|
|