codify-plugin-lib 1.0.178 → 1.0.179
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.d.ts +24 -0
- package/dist/entities/change-set.js +152 -0
- package/dist/entities/errors.d.ts +4 -0
- package/dist/entities/errors.js +7 -0
- package/dist/entities/plan-types.d.ts +25 -0
- package/dist/entities/plan-types.js +1 -0
- package/dist/entities/plan.d.ts +15 -0
- package/dist/entities/plan.js +127 -0
- package/dist/entities/plugin.d.ts +16 -0
- package/dist/entities/plugin.js +80 -0
- package/dist/entities/resource-options.d.ts +31 -0
- package/dist/entities/resource-options.js +76 -0
- package/dist/entities/resource-types.d.ts +11 -0
- package/dist/entities/resource-types.js +1 -0
- package/dist/entities/resource.d.ts +42 -0
- package/dist/entities/resource.js +303 -0
- package/dist/entities/stateful-parameter.d.ts +29 -0
- package/dist/entities/stateful-parameter.js +46 -0
- package/dist/entities/transform-parameter.d.ts +4 -0
- package/dist/entities/transform-parameter.js +2 -0
- package/dist/plan/change-set.d.ts +8 -3
- package/dist/plan/change-set.js +10 -3
- package/dist/plan/plan.js +7 -4
- package/dist/plugin/plugin.d.ts +1 -1
- package/dist/plugin/plugin.js +8 -1
- package/dist/pty/vitest.config.d.ts +2 -0
- package/dist/pty/vitest.config.js +11 -0
- package/dist/resource/resource-settings.d.ts +6 -0
- package/dist/resource/stateful-parameter.d.ts +165 -0
- package/dist/resource/stateful-parameter.js +94 -0
- package/dist/utils/spawn-2.d.ts +5 -0
- package/dist/utils/spawn-2.js +7 -0
- package/dist/utils/spawn.d.ts +29 -0
- package/dist/utils/spawn.js +124 -0
- package/package.json +2 -2
- package/src/plan/change-set.test.ts +1 -1
- package/src/plan/change-set.ts +16 -3
- package/src/plan/plan.ts +7 -4
- package/src/plugin/plugin.ts +9 -1
- package/src/resource/resource-controller-stateful-mode.test.ts +15 -7
- package/src/resource/resource-controller.test.ts +4 -2
- package/src/resource/resource-settings.test.ts +2 -0
- package/src/resource/resource-settings.ts +8 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
|
|
2
|
+
import { ParameterOptions } from './plan-types.js';
|
|
3
|
+
export interface ParameterChange<T extends StringIndexedObject> {
|
|
4
|
+
name: keyof T & string;
|
|
5
|
+
operation: ParameterOperation;
|
|
6
|
+
previousValue: any | null;
|
|
7
|
+
newValue: any | null;
|
|
8
|
+
}
|
|
9
|
+
export declare class ChangeSet<T extends StringIndexedObject> {
|
|
10
|
+
operation: ResourceOperation;
|
|
11
|
+
parameterChanges: Array<ParameterChange<T>>;
|
|
12
|
+
constructor(operation: ResourceOperation, parameterChanges: Array<ParameterChange<T>>);
|
|
13
|
+
get desiredParameters(): T;
|
|
14
|
+
get currentParameters(): T;
|
|
15
|
+
static calculateParameterChangeSet<T extends StringIndexedObject>(desired: T | null, current: T | null, options: {
|
|
16
|
+
statefulMode: boolean;
|
|
17
|
+
parameterOptions?: Record<keyof T, ParameterOptions>;
|
|
18
|
+
}): ParameterChange<T>[];
|
|
19
|
+
static combineResourceOperations(prev: ResourceOperation, next: ResourceOperation): ResourceOperation;
|
|
20
|
+
static isSame(desired: unknown, current: unknown, options?: ParameterOptions): boolean;
|
|
21
|
+
private static calculateStatefulModeChangeSet;
|
|
22
|
+
private static calculateStatelessModeChangeSet;
|
|
23
|
+
private static addDefaultValues;
|
|
24
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
2
|
+
export class ChangeSet {
|
|
3
|
+
operation;
|
|
4
|
+
parameterChanges;
|
|
5
|
+
constructor(operation, parameterChanges) {
|
|
6
|
+
this.operation = operation;
|
|
7
|
+
this.parameterChanges = parameterChanges;
|
|
8
|
+
}
|
|
9
|
+
get desiredParameters() {
|
|
10
|
+
return this.parameterChanges
|
|
11
|
+
.reduce((obj, pc) => ({
|
|
12
|
+
...obj,
|
|
13
|
+
[pc.name]: pc.newValue,
|
|
14
|
+
}), {});
|
|
15
|
+
}
|
|
16
|
+
get currentParameters() {
|
|
17
|
+
return this.parameterChanges
|
|
18
|
+
.reduce((obj, pc) => ({
|
|
19
|
+
...obj,
|
|
20
|
+
[pc.name]: pc.previousValue,
|
|
21
|
+
}), {});
|
|
22
|
+
}
|
|
23
|
+
static calculateParameterChangeSet(desired, current, options) {
|
|
24
|
+
if (options.statefulMode) {
|
|
25
|
+
return ChangeSet.calculateStatefulModeChangeSet(desired, current, options.parameterOptions);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
return ChangeSet.calculateStatelessModeChangeSet(desired, current, options.parameterOptions);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
static combineResourceOperations(prev, next) {
|
|
32
|
+
const orderOfOperations = [
|
|
33
|
+
ResourceOperation.NOOP,
|
|
34
|
+
ResourceOperation.MODIFY,
|
|
35
|
+
ResourceOperation.RECREATE,
|
|
36
|
+
ResourceOperation.CREATE,
|
|
37
|
+
ResourceOperation.DESTROY,
|
|
38
|
+
];
|
|
39
|
+
const indexPrev = orderOfOperations.indexOf(prev);
|
|
40
|
+
const indexNext = orderOfOperations.indexOf(next);
|
|
41
|
+
return orderOfOperations[Math.max(indexPrev, indexNext)];
|
|
42
|
+
}
|
|
43
|
+
static isSame(desired, current, options) {
|
|
44
|
+
if (options?.isEqual) {
|
|
45
|
+
return options.isEqual(desired, current);
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(desired) && Array.isArray(current)) {
|
|
48
|
+
const sortedDesired = desired.map((x) => x).sort();
|
|
49
|
+
const sortedCurrent = current.map((x) => x).sort();
|
|
50
|
+
if (sortedDesired.length !== sortedCurrent.length) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (options?.isElementEqual) {
|
|
54
|
+
return sortedDesired.every((value, index) => options.isElementEqual(value, sortedCurrent[index]));
|
|
55
|
+
}
|
|
56
|
+
return JSON.stringify(sortedDesired) === JSON.stringify(sortedCurrent);
|
|
57
|
+
}
|
|
58
|
+
return desired === current;
|
|
59
|
+
}
|
|
60
|
+
static calculateStatefulModeChangeSet(desired, current, parameterOptions) {
|
|
61
|
+
const parameterChangeSet = new Array();
|
|
62
|
+
const _desired = Object.fromEntries(Object.entries(desired ?? {}).filter(([, v]) => v != null));
|
|
63
|
+
const _current = Object.fromEntries(Object.entries(current ?? {}).filter(([, v]) => v != null));
|
|
64
|
+
this.addDefaultValues(_desired, parameterOptions);
|
|
65
|
+
for (const [k, v] of Object.entries(_current)) {
|
|
66
|
+
if (_desired[k] == null) {
|
|
67
|
+
parameterChangeSet.push({
|
|
68
|
+
name: k,
|
|
69
|
+
previousValue: v,
|
|
70
|
+
newValue: null,
|
|
71
|
+
operation: ParameterOperation.REMOVE,
|
|
72
|
+
});
|
|
73
|
+
delete _current[k];
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (!ChangeSet.isSame(_desired[k], _current[k], parameterOptions?.[k])) {
|
|
77
|
+
parameterChangeSet.push({
|
|
78
|
+
name: k,
|
|
79
|
+
previousValue: v,
|
|
80
|
+
newValue: _desired[k],
|
|
81
|
+
operation: ParameterOperation.MODIFY,
|
|
82
|
+
});
|
|
83
|
+
delete _current[k];
|
|
84
|
+
delete _desired[k];
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
parameterChangeSet.push({
|
|
88
|
+
name: k,
|
|
89
|
+
previousValue: v,
|
|
90
|
+
newValue: _desired[k],
|
|
91
|
+
operation: ParameterOperation.NOOP,
|
|
92
|
+
});
|
|
93
|
+
delete _current[k];
|
|
94
|
+
delete _desired[k];
|
|
95
|
+
}
|
|
96
|
+
if (Object.keys(_current).length !== 0) {
|
|
97
|
+
throw Error('Diff algorithm error');
|
|
98
|
+
}
|
|
99
|
+
for (const [k, v] of Object.entries(_desired)) {
|
|
100
|
+
parameterChangeSet.push({
|
|
101
|
+
name: k,
|
|
102
|
+
previousValue: null,
|
|
103
|
+
newValue: v,
|
|
104
|
+
operation: ParameterOperation.ADD,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return parameterChangeSet;
|
|
108
|
+
}
|
|
109
|
+
static calculateStatelessModeChangeSet(desired, current, parameterOptions) {
|
|
110
|
+
const parameterChangeSet = new Array();
|
|
111
|
+
const _desired = Object.fromEntries(Object.entries(desired ?? {}).filter(([, v]) => v != null));
|
|
112
|
+
const _current = Object.fromEntries(Object.entries(current ?? {}).filter(([, v]) => v != null));
|
|
113
|
+
this.addDefaultValues(_desired, parameterOptions);
|
|
114
|
+
for (const [k, v] of Object.entries(_desired)) {
|
|
115
|
+
if (_current[k] == null) {
|
|
116
|
+
parameterChangeSet.push({
|
|
117
|
+
name: k,
|
|
118
|
+
previousValue: null,
|
|
119
|
+
newValue: v,
|
|
120
|
+
operation: ParameterOperation.ADD,
|
|
121
|
+
});
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!ChangeSet.isSame(_desired[k], _current[k], parameterOptions?.[k])) {
|
|
125
|
+
parameterChangeSet.push({
|
|
126
|
+
name: k,
|
|
127
|
+
previousValue: _current[k],
|
|
128
|
+
newValue: _desired[k],
|
|
129
|
+
operation: ParameterOperation.MODIFY,
|
|
130
|
+
});
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
parameterChangeSet.push({
|
|
134
|
+
name: k,
|
|
135
|
+
previousValue: v,
|
|
136
|
+
newValue: v,
|
|
137
|
+
operation: ParameterOperation.NOOP,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return parameterChangeSet;
|
|
141
|
+
}
|
|
142
|
+
static addDefaultValues(obj, options) {
|
|
143
|
+
Object.entries(options ?? {})
|
|
144
|
+
.filter(([, option]) => option.default !== undefined)
|
|
145
|
+
.map(([name, option]) => [name, option.default])
|
|
146
|
+
.forEach(([key, defaultValue]) => {
|
|
147
|
+
if (obj[key] === undefined) {
|
|
148
|
+
obj[key] = defaultValue;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Plan } from './plan.js';
|
|
2
|
+
import { StringIndexedObject } from 'codify-schemas';
|
|
3
|
+
export interface ParameterOptions {
|
|
4
|
+
modifyOnChange?: boolean;
|
|
5
|
+
isEqual?: (desired: any, current: any) => boolean;
|
|
6
|
+
isElementEqual?: (desired: any, current: any) => boolean;
|
|
7
|
+
default?: unknown;
|
|
8
|
+
isStatefulParameter?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface PlanOptions<T> {
|
|
11
|
+
statefulMode: boolean;
|
|
12
|
+
parameterOptions?: Record<keyof T, ParameterOptions>;
|
|
13
|
+
}
|
|
14
|
+
export interface CreatePlan<T extends StringIndexedObject> extends Plan<T> {
|
|
15
|
+
desiredConfig: T;
|
|
16
|
+
currentConfig: null;
|
|
17
|
+
}
|
|
18
|
+
export interface DestroyPlan<T extends StringIndexedObject> extends Plan<T> {
|
|
19
|
+
desiredConfig: null;
|
|
20
|
+
currentConfig: T;
|
|
21
|
+
}
|
|
22
|
+
export interface ModifyPlan<T extends StringIndexedObject> extends Plan<T> {
|
|
23
|
+
desiredConfig: T;
|
|
24
|
+
currentConfig: T;
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ChangeSet } from './change-set.js';
|
|
2
|
+
import { ApplyRequestData, PlanResponseData, ResourceConfig, StringIndexedObject } from 'codify-schemas';
|
|
3
|
+
import { PlanOptions } from './plan-types.js';
|
|
4
|
+
export declare class Plan<T extends StringIndexedObject> {
|
|
5
|
+
id: string;
|
|
6
|
+
changeSet: ChangeSet<T>;
|
|
7
|
+
resourceMetadata: ResourceConfig;
|
|
8
|
+
constructor(id: string, changeSet: ChangeSet<T>, resourceMetadata: ResourceConfig);
|
|
9
|
+
static create<T extends StringIndexedObject>(desiredParameters: Partial<T> | null, currentParameters: Partial<T> | null, resourceMetadata: ResourceConfig, options: PlanOptions<T>): Plan<T>;
|
|
10
|
+
getResourceType(): string;
|
|
11
|
+
static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues?: Partial<Record<keyof T, unknown>>): Plan<T>;
|
|
12
|
+
get desiredConfig(): T | null;
|
|
13
|
+
get currentConfig(): T | null;
|
|
14
|
+
toResponse(): PlanResponseData;
|
|
15
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { ChangeSet } from './change-set.js';
|
|
2
|
+
import { ParameterOperation, ResourceOperation, } from 'codify-schemas';
|
|
3
|
+
import { randomUUID } from 'crypto';
|
|
4
|
+
export class Plan {
|
|
5
|
+
id;
|
|
6
|
+
changeSet;
|
|
7
|
+
resourceMetadata;
|
|
8
|
+
constructor(id, changeSet, resourceMetadata) {
|
|
9
|
+
this.id = id;
|
|
10
|
+
this.changeSet = changeSet;
|
|
11
|
+
this.resourceMetadata = resourceMetadata;
|
|
12
|
+
}
|
|
13
|
+
static create(desiredParameters, currentParameters, resourceMetadata, options) {
|
|
14
|
+
const parameterOptions = options.parameterOptions ?? {};
|
|
15
|
+
const statefulParameterNames = new Set([...Object.entries(parameterOptions)]
|
|
16
|
+
.filter(([k, v]) => v.isStatefulParameter)
|
|
17
|
+
.map(([k, v]) => k));
|
|
18
|
+
const parameterChangeSet = ChangeSet.calculateParameterChangeSet(desiredParameters, currentParameters, { statefulMode: options.statefulMode, parameterOptions });
|
|
19
|
+
let resourceOperation;
|
|
20
|
+
if (!currentParameters && desiredParameters) {
|
|
21
|
+
resourceOperation = ResourceOperation.CREATE;
|
|
22
|
+
}
|
|
23
|
+
else if (currentParameters && !desiredParameters) {
|
|
24
|
+
resourceOperation = ResourceOperation.DESTROY;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
resourceOperation = parameterChangeSet
|
|
28
|
+
.filter((change) => change.operation !== ParameterOperation.NOOP)
|
|
29
|
+
.reduce((operation, curr) => {
|
|
30
|
+
let newOperation;
|
|
31
|
+
if (statefulParameterNames.has(curr.name)) {
|
|
32
|
+
newOperation = ResourceOperation.MODIFY;
|
|
33
|
+
}
|
|
34
|
+
else if (parameterOptions[curr.name]?.modifyOnChange) {
|
|
35
|
+
newOperation = parameterOptions[curr.name].modifyOnChange ? ResourceOperation.MODIFY : ResourceOperation.RECREATE;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
newOperation = ResourceOperation.RECREATE;
|
|
39
|
+
}
|
|
40
|
+
return ChangeSet.combineResourceOperations(operation, newOperation);
|
|
41
|
+
}, ResourceOperation.NOOP);
|
|
42
|
+
}
|
|
43
|
+
return new Plan(randomUUID(), new ChangeSet(resourceOperation, parameterChangeSet), resourceMetadata);
|
|
44
|
+
}
|
|
45
|
+
getResourceType() {
|
|
46
|
+
return this.resourceMetadata.type;
|
|
47
|
+
}
|
|
48
|
+
static fromResponse(data, defaultValues) {
|
|
49
|
+
if (!data) {
|
|
50
|
+
throw new Error('Data is empty');
|
|
51
|
+
}
|
|
52
|
+
addDefaultValues();
|
|
53
|
+
return new Plan(randomUUID(), new ChangeSet(data.operation, data.parameters), {
|
|
54
|
+
type: data.resourceType,
|
|
55
|
+
name: data.resourceName,
|
|
56
|
+
});
|
|
57
|
+
function addDefaultValues() {
|
|
58
|
+
Object.entries(defaultValues ?? {})
|
|
59
|
+
.forEach(([key, defaultValue]) => {
|
|
60
|
+
const configValueExists = data
|
|
61
|
+
.parameters
|
|
62
|
+
.some((p) => p.name === key);
|
|
63
|
+
if (configValueExists) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
switch (data.operation) {
|
|
67
|
+
case ResourceOperation.CREATE: {
|
|
68
|
+
data.parameters.push({
|
|
69
|
+
name: key,
|
|
70
|
+
operation: ParameterOperation.ADD,
|
|
71
|
+
previousValue: null,
|
|
72
|
+
newValue: defaultValue,
|
|
73
|
+
});
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case ResourceOperation.DESTROY: {
|
|
77
|
+
data.parameters.push({
|
|
78
|
+
name: key,
|
|
79
|
+
operation: ParameterOperation.REMOVE,
|
|
80
|
+
previousValue: defaultValue,
|
|
81
|
+
newValue: null,
|
|
82
|
+
});
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case ResourceOperation.MODIFY:
|
|
86
|
+
case ResourceOperation.RECREATE:
|
|
87
|
+
case ResourceOperation.NOOP: {
|
|
88
|
+
data.parameters.push({
|
|
89
|
+
name: key,
|
|
90
|
+
operation: ParameterOperation.NOOP,
|
|
91
|
+
previousValue: defaultValue,
|
|
92
|
+
newValue: defaultValue,
|
|
93
|
+
});
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
get desiredConfig() {
|
|
101
|
+
if (this.changeSet.operation === ResourceOperation.DESTROY) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
...this.resourceMetadata,
|
|
106
|
+
...this.changeSet.desiredParameters,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
get currentConfig() {
|
|
110
|
+
if (this.changeSet.operation === ResourceOperation.CREATE) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
...this.resourceMetadata,
|
|
115
|
+
...this.changeSet.currentParameters,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
toResponse() {
|
|
119
|
+
return {
|
|
120
|
+
planId: this.id,
|
|
121
|
+
operation: this.changeSet.operation,
|
|
122
|
+
resourceName: this.resourceMetadata.name,
|
|
123
|
+
resourceType: this.resourceMetadata.type,
|
|
124
|
+
parameters: this.changeSet.parameterChanges,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ApplyRequestData, InitializeResponseData, PlanRequestData, PlanResponseData, ResourceConfig, ValidateRequestData, ValidateResponseData } from 'codify-schemas';
|
|
2
|
+
import { Plan } from './plan.js';
|
|
3
|
+
import { Resource } from './resource.js';
|
|
4
|
+
export declare class Plugin {
|
|
5
|
+
name: string;
|
|
6
|
+
resources: Map<string, Resource<ResourceConfig>>;
|
|
7
|
+
planStorage: Map<string, Plan<any>>;
|
|
8
|
+
static create(name: string, resources: Resource<any>[]): Plugin;
|
|
9
|
+
constructor(name: string, resources: Map<string, Resource<ResourceConfig>>);
|
|
10
|
+
initialize(): Promise<InitializeResponseData>;
|
|
11
|
+
validate(data: ValidateRequestData): Promise<ValidateResponseData>;
|
|
12
|
+
plan(data: PlanRequestData): Promise<PlanResponseData>;
|
|
13
|
+
apply(data: ApplyRequestData): Promise<void>;
|
|
14
|
+
private resolvePlan;
|
|
15
|
+
protected crossValidateResources(configs: ResourceConfig[]): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { splitUserConfig } from '../utils/utils.js';
|
|
2
|
+
import { Plan } from './plan.js';
|
|
3
|
+
export class Plugin {
|
|
4
|
+
name;
|
|
5
|
+
resources;
|
|
6
|
+
planStorage;
|
|
7
|
+
static create(name, resources) {
|
|
8
|
+
const resourceMap = new Map(resources.map((r) => [r.typeId, r]));
|
|
9
|
+
return new Plugin(name, resourceMap);
|
|
10
|
+
}
|
|
11
|
+
constructor(name, resources) {
|
|
12
|
+
this.name = name;
|
|
13
|
+
this.resources = resources;
|
|
14
|
+
this.planStorage = new Map();
|
|
15
|
+
}
|
|
16
|
+
async initialize() {
|
|
17
|
+
for (const resource of this.resources.values()) {
|
|
18
|
+
await resource.onInitialize();
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
resourceDefinitions: [...this.resources.values()]
|
|
22
|
+
.map((r) => ({
|
|
23
|
+
dependencies: r.dependencies,
|
|
24
|
+
type: r.typeId,
|
|
25
|
+
}))
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async validate(data) {
|
|
29
|
+
const validationResults = [];
|
|
30
|
+
for (const config of data.configs) {
|
|
31
|
+
if (!this.resources.has(config.type)) {
|
|
32
|
+
throw new Error(`Resource type not found: ${config.type}`);
|
|
33
|
+
}
|
|
34
|
+
const { parameters, resourceMetadata } = splitUserConfig(config);
|
|
35
|
+
const validation = await this.resources
|
|
36
|
+
.get(config.type)
|
|
37
|
+
.validate(parameters, resourceMetadata);
|
|
38
|
+
validationResults.push(validation);
|
|
39
|
+
}
|
|
40
|
+
await this.crossValidateResources(data.configs);
|
|
41
|
+
return {
|
|
42
|
+
resourceValidations: validationResults
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async plan(data) {
|
|
46
|
+
const type = data.desired?.type ?? data.state?.type;
|
|
47
|
+
if (!type || !this.resources.has(type)) {
|
|
48
|
+
throw new Error(`Resource type not found: ${type}`);
|
|
49
|
+
}
|
|
50
|
+
const plan = await this.resources.get(type).plan(data.desired ?? null, data.state ?? null, data.isStateful);
|
|
51
|
+
this.planStorage.set(plan.id, plan);
|
|
52
|
+
return plan.toResponse();
|
|
53
|
+
}
|
|
54
|
+
async apply(data) {
|
|
55
|
+
if (!data.planId && !data.plan) {
|
|
56
|
+
throw new Error('For applies either plan or planId must be supplied');
|
|
57
|
+
}
|
|
58
|
+
const plan = this.resolvePlan(data);
|
|
59
|
+
const resource = this.resources.get(plan.getResourceType());
|
|
60
|
+
if (!resource) {
|
|
61
|
+
throw new Error('Malformed plan with resource that cannot be found');
|
|
62
|
+
}
|
|
63
|
+
await resource.apply(plan);
|
|
64
|
+
}
|
|
65
|
+
resolvePlan(data) {
|
|
66
|
+
const { plan: planRequest, planId } = data;
|
|
67
|
+
if (planId) {
|
|
68
|
+
if (!this.planStorage.has(planId)) {
|
|
69
|
+
throw new Error(`Plan with id: ${planId} was not found`);
|
|
70
|
+
}
|
|
71
|
+
return this.planStorage.get(planId);
|
|
72
|
+
}
|
|
73
|
+
if (!planRequest?.resourceType || !this.resources.has(planRequest.resourceType)) {
|
|
74
|
+
throw new Error('Malformed plan. Resource type must be supplied or resource type was not found');
|
|
75
|
+
}
|
|
76
|
+
const resource = this.resources.get(planRequest.resourceType);
|
|
77
|
+
return Plan.fromResponse(planRequest, resource.defaultValues);
|
|
78
|
+
}
|
|
79
|
+
async crossValidateResources(configs) { }
|
|
80
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { StringIndexedObject } from 'codify-schemas';
|
|
2
|
+
import { ParameterOptions } from './plan-types.js';
|
|
3
|
+
import { ResourceParameterOptions } from './resource-types.js';
|
|
4
|
+
import { StatefulParameter } from './stateful-parameter.js';
|
|
5
|
+
import { TransformParameter } from './transform-parameter.js';
|
|
6
|
+
export interface ResourceOptions<T extends StringIndexedObject> {
|
|
7
|
+
callStatefulParameterRemoveOnDestroy?: boolean;
|
|
8
|
+
dependencies?: string[];
|
|
9
|
+
parameterOptions?: Partial<Record<keyof T, ResourceParameterOptions | ResourceStatefulParameterOptions<T> | ResourceTransformParameterOptions<T>>>;
|
|
10
|
+
schema?: unknown;
|
|
11
|
+
type: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ResourceStatefulParameterOptions<T extends StringIndexedObject> {
|
|
14
|
+
order?: number;
|
|
15
|
+
statefulParameter: StatefulParameter<T, T[keyof T]>;
|
|
16
|
+
}
|
|
17
|
+
export interface ResourceTransformParameterOptions<T extends StringIndexedObject> {
|
|
18
|
+
order?: number;
|
|
19
|
+
transformParameter: TransformParameter<T>;
|
|
20
|
+
}
|
|
21
|
+
export declare class ResourceOptionsParser<T extends StringIndexedObject> {
|
|
22
|
+
private options;
|
|
23
|
+
constructor(options: ResourceOptions<T>);
|
|
24
|
+
get statefulParameters(): Map<keyof T, StatefulParameter<T, T[keyof T]>>;
|
|
25
|
+
get transformParameters(): Map<keyof T, TransformParameter<T>>;
|
|
26
|
+
get resourceParameters(): Map<keyof T, ResourceParameterOptions>;
|
|
27
|
+
get changeSetParameterOptions(): Record<keyof T, ParameterOptions>;
|
|
28
|
+
get defaultValues(): Partial<Record<keyof T, unknown>>;
|
|
29
|
+
get statefulParameterOrder(): Map<keyof T, number>;
|
|
30
|
+
get transformParameterOrder(): Map<keyof T, number>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export class ResourceOptionsParser {
|
|
2
|
+
options;
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this.options = options;
|
|
5
|
+
}
|
|
6
|
+
get statefulParameters() {
|
|
7
|
+
const statefulParameters = Object.entries(this.options.parameterOptions ?? {})
|
|
8
|
+
.filter(([, p]) => p?.hasOwnProperty('statefulParameter'))
|
|
9
|
+
.map(([k, v]) => [k, v])
|
|
10
|
+
.map(([k, v]) => [k, v.statefulParameter]);
|
|
11
|
+
return new Map(statefulParameters);
|
|
12
|
+
}
|
|
13
|
+
get transformParameters() {
|
|
14
|
+
const transformParameters = Object.entries(this.options.parameterOptions ?? {})
|
|
15
|
+
.filter(([, p]) => p?.hasOwnProperty('transformParameter'))
|
|
16
|
+
.map(([k, v]) => [k, v])
|
|
17
|
+
.map(([k, v]) => [k, v.transformParameter]);
|
|
18
|
+
return new Map(transformParameters);
|
|
19
|
+
}
|
|
20
|
+
get resourceParameters() {
|
|
21
|
+
const resourceParameters = Object.entries(this.options.parameterOptions ?? {})
|
|
22
|
+
.filter(([, p]) => !(p?.hasOwnProperty('statefulParameter') || p?.hasOwnProperty('transformParameter')))
|
|
23
|
+
.map(([k, v]) => [k, v]);
|
|
24
|
+
return new Map(resourceParameters);
|
|
25
|
+
}
|
|
26
|
+
get changeSetParameterOptions() {
|
|
27
|
+
const resourceParameters = Object.fromEntries([...this.resourceParameters.entries()]
|
|
28
|
+
.map(([name, value]) => ([name, { ...value, isStatefulParameter: false }])));
|
|
29
|
+
const statefulParameters = [...this.statefulParameters.entries()]
|
|
30
|
+
?.reduce((obj, sp) => ({
|
|
31
|
+
...obj,
|
|
32
|
+
[sp[0]]: {
|
|
33
|
+
...sp[1].options,
|
|
34
|
+
isStatefulParameter: true,
|
|
35
|
+
}
|
|
36
|
+
}), {});
|
|
37
|
+
return {
|
|
38
|
+
...resourceParameters,
|
|
39
|
+
...statefulParameters,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
get defaultValues() {
|
|
43
|
+
if (!this.options.parameterOptions) {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
return Object.fromEntries([...this.resourceParameters.entries()]
|
|
47
|
+
.filter(([, rp]) => rp.default !== undefined)
|
|
48
|
+
.map(([name, rp]) => [name, rp.default]));
|
|
49
|
+
}
|
|
50
|
+
get statefulParameterOrder() {
|
|
51
|
+
const entries = Object.entries(this.options.parameterOptions ?? {})
|
|
52
|
+
.filter(([, v]) => v?.hasOwnProperty('statefulParameter'))
|
|
53
|
+
.map(([k, v]) => [k, v]);
|
|
54
|
+
const orderedEntries = entries.filter(([, v]) => v.order !== undefined);
|
|
55
|
+
const unorderedEntries = entries.filter(([, v]) => v.order === undefined);
|
|
56
|
+
orderedEntries.sort((a, b) => a[1].order - b[1].order);
|
|
57
|
+
const resultArray = [
|
|
58
|
+
...orderedEntries.map(([k]) => k),
|
|
59
|
+
...unorderedEntries.map(([k]) => k)
|
|
60
|
+
];
|
|
61
|
+
return new Map(resultArray.map((key, idx) => [key, idx]));
|
|
62
|
+
}
|
|
63
|
+
get transformParameterOrder() {
|
|
64
|
+
const entries = Object.entries(this.options.parameterOptions ?? {})
|
|
65
|
+
.filter(([, v]) => v?.hasOwnProperty('transformParameter'))
|
|
66
|
+
.map(([k, v]) => [k, v]);
|
|
67
|
+
const orderedEntries = entries.filter(([, v]) => v.order !== undefined);
|
|
68
|
+
const unorderedEntries = entries.filter(([, v]) => v.order === undefined);
|
|
69
|
+
orderedEntries.sort((a, b) => a[1].order - b[1].order);
|
|
70
|
+
const resultArray = [
|
|
71
|
+
...orderedEntries.map(([k]) => k),
|
|
72
|
+
...unorderedEntries.map(([k]) => k)
|
|
73
|
+
];
|
|
74
|
+
return new Map(resultArray.map((key, idx) => [key, idx]));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type ErrorMessage = string;
|
|
2
|
+
export interface ResourceParameterOptions {
|
|
3
|
+
default?: unknown;
|
|
4
|
+
isEqual?: (desired: any, current: any) => boolean;
|
|
5
|
+
modifyOnChange?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ResourceDefinition {
|
|
8
|
+
[x: string]: {
|
|
9
|
+
type: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Ajv, ValidateFunction } from 'ajv';
|
|
2
|
+
import { ResourceConfig, StringIndexedObject, ValidateResponseData } from 'codify-schemas';
|
|
3
|
+
import { ParameterChange } from './change-set.js';
|
|
4
|
+
import { Plan } from './plan.js';
|
|
5
|
+
import { CreatePlan, DestroyPlan, ModifyPlan, ParameterOptions } from './plan-types.js';
|
|
6
|
+
import { ResourceOptions } from './resource-options.js';
|
|
7
|
+
import { ResourceParameterOptions } from './resource-types.js';
|
|
8
|
+
import { StatefulParameter } from './stateful-parameter.js';
|
|
9
|
+
import { TransformParameter } from './transform-parameter.js';
|
|
10
|
+
export declare abstract class Resource<T extends StringIndexedObject> {
|
|
11
|
+
readonly typeId: string;
|
|
12
|
+
readonly statefulParameters: Map<keyof T, StatefulParameter<T, T[keyof T]>>;
|
|
13
|
+
readonly transformParameters: Map<keyof T, TransformParameter<T>>;
|
|
14
|
+
readonly resourceParameters: Map<keyof T, ResourceParameterOptions>;
|
|
15
|
+
readonly statefulParameterOrder: Map<keyof T, number>;
|
|
16
|
+
readonly transformParameterOrder: Map<keyof T, number>;
|
|
17
|
+
readonly dependencies: string[];
|
|
18
|
+
readonly parameterOptions: Record<keyof T, ParameterOptions>;
|
|
19
|
+
readonly options: ResourceOptions<T>;
|
|
20
|
+
readonly defaultValues: Partial<Record<keyof T, unknown>>;
|
|
21
|
+
protected ajv?: Ajv;
|
|
22
|
+
protected schemaValidator?: ValidateFunction;
|
|
23
|
+
protected constructor(options: ResourceOptions<T>);
|
|
24
|
+
onInitialize(): Promise<void>;
|
|
25
|
+
validate(parameters: Partial<T>, resourceMetaData: ResourceConfig): Promise<ValidateResponseData['resourceValidations'][0]>;
|
|
26
|
+
plan(desiredConfig: Partial<T> & ResourceConfig | null, currentConfig?: Partial<T> & ResourceConfig | null, statefulMode?: boolean): Promise<Plan<T>>;
|
|
27
|
+
apply(plan: Plan<T>): Promise<void>;
|
|
28
|
+
private _applyCreate;
|
|
29
|
+
private _applyModify;
|
|
30
|
+
private _applyDestroy;
|
|
31
|
+
private validateRefreshResults;
|
|
32
|
+
private applyTransformParameters;
|
|
33
|
+
private addDefaultValues;
|
|
34
|
+
private refreshNonStatefulParameters;
|
|
35
|
+
private refreshStatefulParameters;
|
|
36
|
+
private validatePlanInputs;
|
|
37
|
+
customValidation(parameters: Partial<T>): Promise<void>;
|
|
38
|
+
abstract refresh(parameters: Partial<T>): Promise<Partial<T> | null>;
|
|
39
|
+
abstract applyCreate(plan: CreatePlan<T>): Promise<void>;
|
|
40
|
+
applyModify(pc: ParameterChange<T>, plan: ModifyPlan<T>): Promise<void>;
|
|
41
|
+
abstract applyDestroy(plan: DestroyPlan<T>): Promise<void>;
|
|
42
|
+
}
|