@codifycli/plugin-core 1.0.0-beta1
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/.eslintignore +2 -0
- package/.eslintrc.json +30 -0
- package/.github/workflows/release.yaml +19 -0
- package/.github/workflows/unit-test-ci.yaml +18 -0
- package/.prettierrc.json +1 -0
- package/bin/build.js +189 -0
- package/dist/bin/build.d.ts +1 -0
- package/dist/bin/build.js +80 -0
- package/dist/bin/deploy-plugin.d.ts +2 -0
- package/dist/bin/deploy-plugin.js +8 -0
- package/dist/common/errors.d.ts +8 -0
- package/dist/common/errors.js +24 -0
- 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/errors.d.ts +4 -0
- package/dist/errors.js +7 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +26 -0
- package/dist/messages/handlers.d.ts +14 -0
- package/dist/messages/handlers.js +134 -0
- package/dist/messages/sender.d.ts +11 -0
- package/dist/messages/sender.js +57 -0
- package/dist/plan/change-set.d.ts +53 -0
- package/dist/plan/change-set.js +153 -0
- package/dist/plan/plan-types.d.ts +23 -0
- package/dist/plan/plan-types.js +1 -0
- package/dist/plan/plan.d.ts +66 -0
- package/dist/plan/plan.js +328 -0
- package/dist/plugin/plugin.d.ts +24 -0
- package/dist/plugin/plugin.js +200 -0
- package/dist/pty/background-pty.d.ts +21 -0
- package/dist/pty/background-pty.js +127 -0
- package/dist/pty/index.d.ts +50 -0
- package/dist/pty/index.js +20 -0
- package/dist/pty/promise-queue.d.ts +5 -0
- package/dist/pty/promise-queue.js +26 -0
- package/dist/pty/seqeuntial-pty.d.ts +17 -0
- package/dist/pty/seqeuntial-pty.js +119 -0
- package/dist/pty/vitest.config.d.ts +2 -0
- package/dist/pty/vitest.config.js +11 -0
- package/dist/resource/config-parser.d.ts +11 -0
- package/dist/resource/config-parser.js +21 -0
- package/dist/resource/parsed-resource-settings.d.ts +47 -0
- package/dist/resource/parsed-resource-settings.js +196 -0
- package/dist/resource/resource-controller.d.ts +36 -0
- package/dist/resource/resource-controller.js +402 -0
- package/dist/resource/resource-settings.d.ts +303 -0
- package/dist/resource/resource-settings.js +147 -0
- package/dist/resource/resource.d.ts +144 -0
- package/dist/resource/resource.js +44 -0
- package/dist/resource/stateful-parameter.d.ts +165 -0
- package/dist/resource/stateful-parameter.js +94 -0
- package/dist/scripts/deploy.d.ts +1 -0
- package/dist/scripts/deploy.js +2 -0
- package/dist/stateful-parameter/stateful-parameter-controller.d.ts +21 -0
- package/dist/stateful-parameter/stateful-parameter-controller.js +81 -0
- package/dist/stateful-parameter/stateful-parameter.d.ts +144 -0
- package/dist/stateful-parameter/stateful-parameter.js +43 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +5 -0
- package/dist/utils/codify-spawn.d.ts +29 -0
- package/dist/utils/codify-spawn.js +136 -0
- package/dist/utils/debug.d.ts +2 -0
- package/dist/utils/debug.js +10 -0
- package/dist/utils/file-utils.d.ts +23 -0
- package/dist/utils/file-utils.js +186 -0
- package/dist/utils/functions.d.ts +12 -0
- package/dist/utils/functions.js +74 -0
- package/dist/utils/index.d.ts +46 -0
- package/dist/utils/index.js +271 -0
- package/dist/utils/internal-utils.d.ts +12 -0
- package/dist/utils/internal-utils.js +74 -0
- package/dist/utils/load-resources.d.ts +1 -0
- package/dist/utils/load-resources.js +46 -0
- package/dist/utils/package-json-utils.d.ts +12 -0
- package/dist/utils/package-json-utils.js +34 -0
- package/dist/utils/pty-local-storage.d.ts +2 -0
- package/dist/utils/pty-local-storage.js +2 -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/dist/utils/utils.d.ts +18 -0
- package/dist/utils/utils.js +86 -0
- package/dist/utils/verbosity-level.d.ts +5 -0
- package/dist/utils/verbosity-level.js +9 -0
- package/package.json +59 -0
- package/rollup.config.js +24 -0
- package/src/common/errors.test.ts +43 -0
- package/src/common/errors.ts +31 -0
- package/src/errors.ts +8 -0
- package/src/index.test.ts +6 -0
- package/src/index.ts +30 -0
- package/src/messages/handlers.test.ts +329 -0
- package/src/messages/handlers.ts +181 -0
- package/src/messages/sender.ts +69 -0
- package/src/plan/change-set.test.ts +280 -0
- package/src/plan/change-set.ts +236 -0
- package/src/plan/plan-types.ts +27 -0
- package/src/plan/plan.test.ts +413 -0
- package/src/plan/plan.ts +499 -0
- package/src/plugin/plugin.test.ts +533 -0
- package/src/plugin/plugin.ts +291 -0
- package/src/pty/background-pty.test.ts +69 -0
- package/src/pty/background-pty.ts +154 -0
- package/src/pty/index.test.ts +129 -0
- package/src/pty/index.ts +66 -0
- package/src/pty/promise-queue.ts +33 -0
- package/src/pty/seqeuntial-pty.ts +151 -0
- package/src/pty/sequential-pty.test.ts +194 -0
- package/src/resource/config-parser.ts +42 -0
- package/src/resource/parsed-resource-settings.test.ts +186 -0
- package/src/resource/parsed-resource-settings.ts +307 -0
- package/src/resource/resource-controller-stateful-mode.test.ts +253 -0
- package/src/resource/resource-controller.test.ts +1081 -0
- package/src/resource/resource-controller.ts +563 -0
- package/src/resource/resource-settings.test.ts +1213 -0
- package/src/resource/resource-settings.ts +545 -0
- package/src/resource/resource.ts +157 -0
- package/src/stateful-parameter/stateful-parameter-controller.test.ts +244 -0
- package/src/stateful-parameter/stateful-parameter-controller.ts +111 -0
- package/src/stateful-parameter/stateful-parameter.ts +160 -0
- package/src/utils/debug.ts +11 -0
- package/src/utils/file-utils.test.ts +7 -0
- package/src/utils/file-utils.ts +231 -0
- package/src/utils/functions.ts +103 -0
- package/src/utils/index.ts +340 -0
- package/src/utils/internal-utils.test.ts +52 -0
- package/src/utils/pty-local-storage.ts +3 -0
- package/src/utils/test-utils.test.ts +96 -0
- package/src/utils/verbosity-level.ts +11 -0
- package/tsconfig.json +26 -0
- package/tsconfig.test.json +9 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
|
|
2
|
+
import { ParsedParameterSetting } from '../resource/parsed-resource-settings.js';
|
|
3
|
+
import { ResourceSettings } from '../resource/resource-settings.js';
|
|
4
|
+
/**
|
|
5
|
+
* A parameter change describes a parameter level change to a resource.
|
|
6
|
+
*/
|
|
7
|
+
export interface ParameterChange<T extends StringIndexedObject> {
|
|
8
|
+
/**
|
|
9
|
+
* The name of the parameter
|
|
10
|
+
*/
|
|
11
|
+
name: keyof T & string;
|
|
12
|
+
/**
|
|
13
|
+
* The operation to be performed on the parameter.
|
|
14
|
+
*/
|
|
15
|
+
operation: ParameterOperation;
|
|
16
|
+
/**
|
|
17
|
+
* The previous value of the resource (the current value on the system)
|
|
18
|
+
*/
|
|
19
|
+
previousValue: any | null;
|
|
20
|
+
/**
|
|
21
|
+
* The new value of the resource (the desired value)
|
|
22
|
+
*/
|
|
23
|
+
newValue: any | null;
|
|
24
|
+
/**
|
|
25
|
+
* Whether the parameter is sensitive
|
|
26
|
+
*/
|
|
27
|
+
isSensitive: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare class ChangeSet<T extends StringIndexedObject> {
|
|
30
|
+
operation: ResourceOperation;
|
|
31
|
+
parameterChanges: Array<ParameterChange<T>>;
|
|
32
|
+
constructor(operation: ResourceOperation, parameterChanges: Array<ParameterChange<T>>);
|
|
33
|
+
get desiredParameters(): T;
|
|
34
|
+
get currentParameters(): T;
|
|
35
|
+
static empty<T extends StringIndexedObject>(): ChangeSet<T>;
|
|
36
|
+
static create<T extends StringIndexedObject>(desired: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T>;
|
|
37
|
+
static noop<T extends StringIndexedObject>(parameters: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T>;
|
|
38
|
+
static destroy<T extends StringIndexedObject>(current: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T>;
|
|
39
|
+
static calculateModification<T extends StringIndexedObject>(desired: Partial<T>, current: Partial<T>, parameterSettings?: Partial<Record<keyof T, ParsedParameterSetting>>): ChangeSet<T>;
|
|
40
|
+
/**
|
|
41
|
+
* Calculates the differences between the desired and current parameters,
|
|
42
|
+
* and returns a list of parameter changes that describe what needs to be added,
|
|
43
|
+
* removed, or modified to match the desired state.
|
|
44
|
+
*
|
|
45
|
+
* @param {Partial<T>} desiredParameters - The desired target state of the parameters.
|
|
46
|
+
* @param {Partial<T>} currentParameters - The current state of the parameters.
|
|
47
|
+
* @param {Partial<Record<keyof T, ParameterSetting>>} [parameterOptions] - Optional settings used when comparing parameters.
|
|
48
|
+
* @return {ParameterChange<T>[]} A list of changes required to transition from the current state to the desired state.
|
|
49
|
+
*/
|
|
50
|
+
private static calculateParameterChanges;
|
|
51
|
+
private static combineResourceOperations;
|
|
52
|
+
private static isSame;
|
|
53
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
2
|
+
// Change set will coerce undefined values to null because undefined is not valid JSON
|
|
3
|
+
export class ChangeSet {
|
|
4
|
+
operation;
|
|
5
|
+
parameterChanges;
|
|
6
|
+
constructor(operation, parameterChanges) {
|
|
7
|
+
this.operation = operation;
|
|
8
|
+
this.parameterChanges = parameterChanges;
|
|
9
|
+
}
|
|
10
|
+
get desiredParameters() {
|
|
11
|
+
return this.parameterChanges
|
|
12
|
+
.reduce((obj, pc) => ({
|
|
13
|
+
...obj,
|
|
14
|
+
[pc.name]: pc.newValue,
|
|
15
|
+
}), {});
|
|
16
|
+
}
|
|
17
|
+
get currentParameters() {
|
|
18
|
+
return this.parameterChanges
|
|
19
|
+
.reduce((obj, pc) => ({
|
|
20
|
+
...obj,
|
|
21
|
+
[pc.name]: pc.previousValue,
|
|
22
|
+
}), {});
|
|
23
|
+
}
|
|
24
|
+
static empty() {
|
|
25
|
+
return new ChangeSet(ResourceOperation.NOOP, []);
|
|
26
|
+
}
|
|
27
|
+
static create(desired, settings) {
|
|
28
|
+
const parameterChanges = Object.entries(desired)
|
|
29
|
+
.map(([k, v]) => ({
|
|
30
|
+
name: k,
|
|
31
|
+
operation: ParameterOperation.ADD,
|
|
32
|
+
previousValue: null,
|
|
33
|
+
newValue: v ?? null,
|
|
34
|
+
isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
|
|
35
|
+
}));
|
|
36
|
+
return new ChangeSet(ResourceOperation.CREATE, parameterChanges);
|
|
37
|
+
}
|
|
38
|
+
static noop(parameters, settings) {
|
|
39
|
+
const parameterChanges = Object.entries(parameters)
|
|
40
|
+
.map(([k, v]) => ({
|
|
41
|
+
name: k,
|
|
42
|
+
operation: ParameterOperation.NOOP,
|
|
43
|
+
previousValue: v ?? null,
|
|
44
|
+
newValue: v ?? null,
|
|
45
|
+
isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
|
|
46
|
+
}));
|
|
47
|
+
return new ChangeSet(ResourceOperation.NOOP, parameterChanges);
|
|
48
|
+
}
|
|
49
|
+
static destroy(current, settings) {
|
|
50
|
+
const parameterChanges = Object.entries(current)
|
|
51
|
+
.map(([k, v]) => ({
|
|
52
|
+
name: k,
|
|
53
|
+
operation: ParameterOperation.REMOVE,
|
|
54
|
+
previousValue: v ?? null,
|
|
55
|
+
newValue: null,
|
|
56
|
+
isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
|
|
57
|
+
}));
|
|
58
|
+
return new ChangeSet(ResourceOperation.DESTROY, parameterChanges);
|
|
59
|
+
}
|
|
60
|
+
static calculateModification(desired, current, parameterSettings = {}) {
|
|
61
|
+
const pc = ChangeSet.calculateParameterChanges(desired, current, parameterSettings);
|
|
62
|
+
const statefulParameterKeys = new Set(Object.entries(parameterSettings)
|
|
63
|
+
.filter(([, v]) => v?.type === 'stateful')
|
|
64
|
+
.map(([k]) => k));
|
|
65
|
+
const resourceOperation = pc
|
|
66
|
+
.filter((change) => change.operation !== ParameterOperation.NOOP)
|
|
67
|
+
.reduce((operation, curr) => {
|
|
68
|
+
let newOperation;
|
|
69
|
+
if (statefulParameterKeys.has(curr.name)) {
|
|
70
|
+
newOperation = ResourceOperation.MODIFY; // All stateful parameters are modify only
|
|
71
|
+
}
|
|
72
|
+
else if (parameterSettings[curr.name]?.canModify) {
|
|
73
|
+
newOperation = ResourceOperation.MODIFY;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
newOperation = ResourceOperation.RECREATE; // Default to Re-create. Should handle the majority of use cases
|
|
77
|
+
}
|
|
78
|
+
return ChangeSet.combineResourceOperations(operation, newOperation);
|
|
79
|
+
}, ResourceOperation.NOOP);
|
|
80
|
+
return new ChangeSet(resourceOperation, pc);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Calculates the differences between the desired and current parameters,
|
|
84
|
+
* and returns a list of parameter changes that describe what needs to be added,
|
|
85
|
+
* removed, or modified to match the desired state.
|
|
86
|
+
*
|
|
87
|
+
* @param {Partial<T>} desiredParameters - The desired target state of the parameters.
|
|
88
|
+
* @param {Partial<T>} currentParameters - The current state of the parameters.
|
|
89
|
+
* @param {Partial<Record<keyof T, ParameterSetting>>} [parameterOptions] - Optional settings used when comparing parameters.
|
|
90
|
+
* @return {ParameterChange<T>[]} A list of changes required to transition from the current state to the desired state.
|
|
91
|
+
*/
|
|
92
|
+
static calculateParameterChanges(desiredParameters, currentParameters, parameterOptions) {
|
|
93
|
+
const parameterChangeSet = new Array();
|
|
94
|
+
// Filter out null and undefined values or else the diff below will not work
|
|
95
|
+
const desired = Object.fromEntries(Object.entries(desiredParameters).filter(([, v]) => v !== null && v !== undefined));
|
|
96
|
+
const current = Object.fromEntries(Object.entries(currentParameters).filter(([, v]) => v !== null && v !== undefined));
|
|
97
|
+
for (const k of new Set([...Object.keys(current), ...Object.keys(desired)])) {
|
|
98
|
+
if (ChangeSet.isSame(desired[k], current[k], parameterOptions?.[k])) {
|
|
99
|
+
parameterChangeSet.push({
|
|
100
|
+
name: k,
|
|
101
|
+
previousValue: current[k] ?? null,
|
|
102
|
+
newValue: desired[k] ?? null,
|
|
103
|
+
operation: ParameterOperation.NOOP,
|
|
104
|
+
isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
|
|
105
|
+
});
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if ((desired?.[k] === null || desired?.[k] === undefined) && (current?.[k] !== null && current?.[k] !== undefined)) {
|
|
109
|
+
parameterChangeSet.push({
|
|
110
|
+
name: k,
|
|
111
|
+
previousValue: current[k] ?? null,
|
|
112
|
+
newValue: null,
|
|
113
|
+
operation: ParameterOperation.REMOVE,
|
|
114
|
+
isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
|
|
115
|
+
});
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if ((current?.[k] === null || current?.[k] === undefined) && (desired?.[k] !== null && desired?.[k] !== undefined)) {
|
|
119
|
+
parameterChangeSet.push({
|
|
120
|
+
name: k,
|
|
121
|
+
previousValue: null,
|
|
122
|
+
newValue: desired[k] ?? null,
|
|
123
|
+
operation: ParameterOperation.ADD,
|
|
124
|
+
isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
|
|
125
|
+
});
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
parameterChangeSet.push({
|
|
129
|
+
name: k,
|
|
130
|
+
previousValue: current[k] ?? null,
|
|
131
|
+
newValue: desired[k] ?? null,
|
|
132
|
+
operation: ParameterOperation.MODIFY,
|
|
133
|
+
isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return parameterChangeSet;
|
|
137
|
+
}
|
|
138
|
+
static combineResourceOperations(prev, next) {
|
|
139
|
+
const orderOfOperations = [
|
|
140
|
+
ResourceOperation.NOOP,
|
|
141
|
+
ResourceOperation.MODIFY,
|
|
142
|
+
ResourceOperation.RECREATE,
|
|
143
|
+
ResourceOperation.CREATE,
|
|
144
|
+
ResourceOperation.DESTROY,
|
|
145
|
+
];
|
|
146
|
+
const indexPrev = orderOfOperations.indexOf(prev);
|
|
147
|
+
const indexNext = orderOfOperations.indexOf(next);
|
|
148
|
+
return orderOfOperations[Math.max(indexPrev, indexNext)];
|
|
149
|
+
}
|
|
150
|
+
static isSame(desired, current, setting) {
|
|
151
|
+
return (setting?.isEqual ?? ((a, b) => a === b))(desired, current);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { StringIndexedObject } from 'codify-schemas';
|
|
2
|
+
import { Plan } from './plan.js';
|
|
3
|
+
/**
|
|
4
|
+
* A narrower type for plans for CREATE operations. Only desiredConfig is not null.
|
|
5
|
+
*/
|
|
6
|
+
export interface CreatePlan<T extends StringIndexedObject> extends Plan<T> {
|
|
7
|
+
desiredConfig: T;
|
|
8
|
+
currentConfig: null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A narrower type for plans for DESTROY operations. Only currentConfig is not null.
|
|
12
|
+
*/
|
|
13
|
+
export interface DestroyPlan<T extends StringIndexedObject> extends Plan<T> {
|
|
14
|
+
desiredConfig: null;
|
|
15
|
+
currentConfig: T;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A narrower type for plans for MODIFY and RE-CREATE operations.
|
|
19
|
+
*/
|
|
20
|
+
export interface ModifyPlan<T extends StringIndexedObject> extends Plan<T> {
|
|
21
|
+
desiredConfig: T;
|
|
22
|
+
currentConfig: T;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ApplyRequestData, PlanResponseData, ResourceConfig, StringIndexedObject } from 'codify-schemas';
|
|
2
|
+
import { ParsedResourceSettings } from '../resource/parsed-resource-settings.js';
|
|
3
|
+
import { ChangeSet } from './change-set.js';
|
|
4
|
+
/**
|
|
5
|
+
* A plan represents a set of actions that after taken will turn the current resource into the desired one.
|
|
6
|
+
* A plan consists of list of parameter level changes (ADD, REMOVE, MODIFY or NO-OP) as well as a resource level
|
|
7
|
+
* operation (CREATE, DESTROY, MODIFY, RE-CREATE, NO-OP).
|
|
8
|
+
*/
|
|
9
|
+
export declare class Plan<T extends StringIndexedObject> {
|
|
10
|
+
id: string;
|
|
11
|
+
/**
|
|
12
|
+
* List of changes to make
|
|
13
|
+
*/
|
|
14
|
+
changeSet: ChangeSet<T>;
|
|
15
|
+
/**
|
|
16
|
+
* Ex: name, type, dependsOn etc. Metadata parameters
|
|
17
|
+
*/
|
|
18
|
+
coreParameters: ResourceConfig;
|
|
19
|
+
isStateful: boolean;
|
|
20
|
+
constructor(id: string, changeSet: ChangeSet<T>, coreParameters: ResourceConfig, isStateful: boolean);
|
|
21
|
+
/**
|
|
22
|
+
* The desired config that a plan will achieve after executing all the actions.
|
|
23
|
+
*/
|
|
24
|
+
get desiredConfig(): T | null;
|
|
25
|
+
/**
|
|
26
|
+
* The current config that the plan is changing.
|
|
27
|
+
*/
|
|
28
|
+
get currentConfig(): T | null;
|
|
29
|
+
get resourceId(): string;
|
|
30
|
+
static calculate<T extends StringIndexedObject>(params: {
|
|
31
|
+
desired: Partial<T> | null;
|
|
32
|
+
currentArray: Partial<T>[] | null;
|
|
33
|
+
state: Partial<T> | null;
|
|
34
|
+
core: ResourceConfig;
|
|
35
|
+
settings: ParsedResourceSettings<T>;
|
|
36
|
+
isStateful: boolean;
|
|
37
|
+
}): Plan<T>;
|
|
38
|
+
static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues?: Partial<Record<keyof T, unknown>>): Plan<T>;
|
|
39
|
+
/**
|
|
40
|
+
* The type (id) of the resource
|
|
41
|
+
*
|
|
42
|
+
* @return string
|
|
43
|
+
*/
|
|
44
|
+
getResourceType(): string;
|
|
45
|
+
/**
|
|
46
|
+
* When multiples of the same resource are allowed, this matching function will match a given config with one of the
|
|
47
|
+
* existing configs on the system. For example if there are multiple versions of Android Studios installed, we can use
|
|
48
|
+
* the application name and location to match it to our desired configs name and location.
|
|
49
|
+
*
|
|
50
|
+
* @param params
|
|
51
|
+
* @private
|
|
52
|
+
*/
|
|
53
|
+
private static matchCurrentParameters;
|
|
54
|
+
/**
|
|
55
|
+
* Only keep relevant params for the plan. We don't want to change settings that were not already
|
|
56
|
+
* defined.
|
|
57
|
+
*
|
|
58
|
+
* 1. In stateless mode, filter current by desired. We only want to know about settings that the user has specified
|
|
59
|
+
* 2. In stateful mode, filter current by state and desired. We only know about the settings the user has previously set
|
|
60
|
+
* or wants to set. If a parameter is not specified then it's not managed by Codify.
|
|
61
|
+
*/
|
|
62
|
+
private static filterCurrentParams;
|
|
63
|
+
requiresChanges(): boolean;
|
|
64
|
+
/** Convert the plan to a JSON response object */
|
|
65
|
+
toResponse(): PlanResponseData;
|
|
66
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { ParameterOperation, ResourceOperation, } from 'codify-schemas';
|
|
2
|
+
import { v4 as uuidV4 } from 'uuid';
|
|
3
|
+
import { ChangeSet } from './change-set.js';
|
|
4
|
+
/**
|
|
5
|
+
* A plan represents a set of actions that after taken will turn the current resource into the desired one.
|
|
6
|
+
* A plan consists of list of parameter level changes (ADD, REMOVE, MODIFY or NO-OP) as well as a resource level
|
|
7
|
+
* operation (CREATE, DESTROY, MODIFY, RE-CREATE, NO-OP).
|
|
8
|
+
*/
|
|
9
|
+
export class Plan {
|
|
10
|
+
id;
|
|
11
|
+
/**
|
|
12
|
+
* List of changes to make
|
|
13
|
+
*/
|
|
14
|
+
changeSet;
|
|
15
|
+
/**
|
|
16
|
+
* Ex: name, type, dependsOn etc. Metadata parameters
|
|
17
|
+
*/
|
|
18
|
+
coreParameters;
|
|
19
|
+
isStateful;
|
|
20
|
+
constructor(id, changeSet, coreParameters, isStateful) {
|
|
21
|
+
this.id = id;
|
|
22
|
+
this.changeSet = changeSet;
|
|
23
|
+
this.coreParameters = coreParameters;
|
|
24
|
+
this.isStateful = isStateful;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The desired config that a plan will achieve after executing all the actions.
|
|
28
|
+
*/
|
|
29
|
+
get desiredConfig() {
|
|
30
|
+
if (this.changeSet.operation === ResourceOperation.DESTROY) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return this.changeSet.desiredParameters;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The current config that the plan is changing.
|
|
37
|
+
*/
|
|
38
|
+
get currentConfig() {
|
|
39
|
+
if (this.changeSet.operation === ResourceOperation.CREATE) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return this.changeSet.currentParameters;
|
|
43
|
+
}
|
|
44
|
+
get resourceId() {
|
|
45
|
+
return this.coreParameters.name
|
|
46
|
+
? `${this.coreParameters.type}.${this.coreParameters.name}`
|
|
47
|
+
: this.coreParameters.type;
|
|
48
|
+
}
|
|
49
|
+
static calculate(params) {
|
|
50
|
+
const { desired, currentArray, state, core, settings, isStateful } = params;
|
|
51
|
+
const current = Plan.matchCurrentParameters({
|
|
52
|
+
desired,
|
|
53
|
+
currentArray,
|
|
54
|
+
state,
|
|
55
|
+
settings,
|
|
56
|
+
isStateful
|
|
57
|
+
});
|
|
58
|
+
const filteredCurrentParameters = Plan.filterCurrentParams({
|
|
59
|
+
desired,
|
|
60
|
+
current,
|
|
61
|
+
state,
|
|
62
|
+
settings,
|
|
63
|
+
isStateful
|
|
64
|
+
});
|
|
65
|
+
// Empty
|
|
66
|
+
if (!filteredCurrentParameters && !desired) {
|
|
67
|
+
return new Plan(uuidV4(), ChangeSet.empty(), core, isStateful);
|
|
68
|
+
}
|
|
69
|
+
// CREATE
|
|
70
|
+
if (!filteredCurrentParameters && desired) {
|
|
71
|
+
return new Plan(uuidV4(), ChangeSet.create(desired, settings), core, isStateful);
|
|
72
|
+
}
|
|
73
|
+
// DESTROY
|
|
74
|
+
if (filteredCurrentParameters && !desired) {
|
|
75
|
+
// We can manually override destroys. If a resource cannot be destroyed (for instance the npm resource relies on NodeJS being created and destroyed)
|
|
76
|
+
if (!settings.canDestroy) {
|
|
77
|
+
return new Plan(uuidV4(), ChangeSet.noop(filteredCurrentParameters, settings), core, isStateful);
|
|
78
|
+
}
|
|
79
|
+
return new Plan(uuidV4(), ChangeSet.destroy(filteredCurrentParameters, settings), core, isStateful);
|
|
80
|
+
}
|
|
81
|
+
// NO-OP, MODIFY or RE-CREATE
|
|
82
|
+
const changeSet = ChangeSet.calculateModification(desired, filteredCurrentParameters, settings.parameterSettings);
|
|
83
|
+
return new Plan(uuidV4(), changeSet, core, isStateful);
|
|
84
|
+
}
|
|
85
|
+
// 2. Even if there was (maybe for testing reasons), the plan values should not be adjusted
|
|
86
|
+
static fromResponse(data, defaultValues) {
|
|
87
|
+
if (!data) {
|
|
88
|
+
throw new Error('Data is empty');
|
|
89
|
+
}
|
|
90
|
+
addDefaultValues();
|
|
91
|
+
return new Plan(uuidV4(), new ChangeSet(data.operation, data.parameters.map((p) => ({
|
|
92
|
+
...p,
|
|
93
|
+
isSensitive: p.isSensitive ?? false,
|
|
94
|
+
}))), {
|
|
95
|
+
type: data.resourceType,
|
|
96
|
+
name: data.resourceName,
|
|
97
|
+
}, data.isStateful);
|
|
98
|
+
function addDefaultValues() {
|
|
99
|
+
Object.entries(defaultValues ?? {})
|
|
100
|
+
.forEach(([key, defaultValue]) => {
|
|
101
|
+
const configValueExists = data
|
|
102
|
+
.parameters
|
|
103
|
+
.some((p) => p.name === key);
|
|
104
|
+
// Only set default values if the value does not exist in the config
|
|
105
|
+
if (configValueExists) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
switch (data.operation) {
|
|
109
|
+
case ResourceOperation.CREATE: {
|
|
110
|
+
data.parameters.push({
|
|
111
|
+
name: key,
|
|
112
|
+
operation: ParameterOperation.ADD,
|
|
113
|
+
previousValue: null,
|
|
114
|
+
newValue: defaultValue,
|
|
115
|
+
});
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case ResourceOperation.DESTROY: {
|
|
119
|
+
data.parameters.push({
|
|
120
|
+
name: key,
|
|
121
|
+
operation: ParameterOperation.REMOVE,
|
|
122
|
+
previousValue: defaultValue,
|
|
123
|
+
newValue: null,
|
|
124
|
+
});
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case ResourceOperation.MODIFY:
|
|
128
|
+
case ResourceOperation.RECREATE:
|
|
129
|
+
case ResourceOperation.NOOP: {
|
|
130
|
+
data.parameters.push({
|
|
131
|
+
name: key,
|
|
132
|
+
operation: ParameterOperation.NOOP,
|
|
133
|
+
previousValue: defaultValue,
|
|
134
|
+
newValue: defaultValue,
|
|
135
|
+
});
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The type (id) of the resource
|
|
144
|
+
*
|
|
145
|
+
* @return string
|
|
146
|
+
*/
|
|
147
|
+
getResourceType() {
|
|
148
|
+
return this.coreParameters.type;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* When multiples of the same resource are allowed, this matching function will match a given config with one of the
|
|
152
|
+
* existing configs on the system. For example if there are multiple versions of Android Studios installed, we can use
|
|
153
|
+
* the application name and location to match it to our desired configs name and location.
|
|
154
|
+
*
|
|
155
|
+
* @param params
|
|
156
|
+
* @private
|
|
157
|
+
*/
|
|
158
|
+
static matchCurrentParameters(params) {
|
|
159
|
+
const { desired, currentArray, state, settings, isStateful } = params;
|
|
160
|
+
if (!settings.allowMultiple) {
|
|
161
|
+
return currentArray?.[0] ?? null;
|
|
162
|
+
}
|
|
163
|
+
if (!currentArray) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const { matcher: parameterMatcher, id } = settings;
|
|
167
|
+
const matcher = (desired, currentArray) => {
|
|
168
|
+
const matched = currentArray.filter((c) => parameterMatcher(desired, c));
|
|
169
|
+
if (matched.length > 1) {
|
|
170
|
+
console.log(`Resource: ${id} did not uniquely match resources when allow multiple is set to true`);
|
|
171
|
+
}
|
|
172
|
+
return matched[0];
|
|
173
|
+
};
|
|
174
|
+
if (isStateful) {
|
|
175
|
+
return state
|
|
176
|
+
? matcher(state, currentArray) ?? null
|
|
177
|
+
: null;
|
|
178
|
+
}
|
|
179
|
+
return matcher(desired, currentArray) ?? null;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Only keep relevant params for the plan. We don't want to change settings that were not already
|
|
183
|
+
* defined.
|
|
184
|
+
*
|
|
185
|
+
* 1. In stateless mode, filter current by desired. We only want to know about settings that the user has specified
|
|
186
|
+
* 2. In stateful mode, filter current by state and desired. We only know about the settings the user has previously set
|
|
187
|
+
* or wants to set. If a parameter is not specified then it's not managed by Codify.
|
|
188
|
+
*/
|
|
189
|
+
static filterCurrentParams(params) {
|
|
190
|
+
const { desired, current, state, settings, isStateful } = params;
|
|
191
|
+
if (!current) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const filteredCurrent = filterCurrent();
|
|
195
|
+
if (!filteredCurrent) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
// For stateful mode, we're done after filtering by the keys of desired + state. Stateless mode
|
|
199
|
+
// requires additional filtering for stateful parameter arrays and objects.
|
|
200
|
+
if (isStateful && desired) {
|
|
201
|
+
return filteredCurrent;
|
|
202
|
+
}
|
|
203
|
+
// We also want to filter parameters when in delete mode. We don't want to delete parameters that
|
|
204
|
+
// are not specified in the original config.
|
|
205
|
+
if (isStateful && !desired) {
|
|
206
|
+
const arrayStatefulParameters = Object.fromEntries(Object.entries(filteredCurrent)
|
|
207
|
+
.filter(([k, v]) => isArrayParameterWithFiltering(k, v))
|
|
208
|
+
.map(([k, v]) => [k, filterArrayParameterForDeletes(k, v)]));
|
|
209
|
+
return { ...filteredCurrent, ...arrayStatefulParameters };
|
|
210
|
+
}
|
|
211
|
+
// TODO: Add object handling here in addition to arrays in the future
|
|
212
|
+
const arrayStatefulParameters = Object.fromEntries(Object.entries(filteredCurrent)
|
|
213
|
+
.filter(([k, v]) => isArrayParameterWithFiltering(k, v))
|
|
214
|
+
.map(([k, v]) => [k, filterArrayParameterForStatelessMode(k, v)]));
|
|
215
|
+
return { ...filteredCurrent, ...arrayStatefulParameters };
|
|
216
|
+
function filterCurrent() {
|
|
217
|
+
if (!current) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
if (isStateful) {
|
|
221
|
+
const keys = new Set([...Object.keys(state ?? {}), ...Object.keys(desired ?? {})]);
|
|
222
|
+
return Object.fromEntries(Object.entries(current)
|
|
223
|
+
.filter(([k]) => keys.has(k)));
|
|
224
|
+
}
|
|
225
|
+
// Stateless mode
|
|
226
|
+
const keys = new Set(Object.keys(desired ?? {}));
|
|
227
|
+
return Object.fromEntries(Object.entries(current)
|
|
228
|
+
.filter(([k]) => keys.has(k)));
|
|
229
|
+
}
|
|
230
|
+
function getFilterParameter(k) {
|
|
231
|
+
if (settings.parameterSettings?.[k]?.type === 'stateful') {
|
|
232
|
+
const statefulSetting = settings.parameterSettings[k];
|
|
233
|
+
if (statefulSetting.nestedSettings.type === 'array') {
|
|
234
|
+
return statefulSetting.nestedSettings.filterInStatelessMode;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (settings.parameterSettings?.[k]?.type === 'array') {
|
|
238
|
+
return (settings.parameterSettings?.[k]).filterInStatelessMode;
|
|
239
|
+
}
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
function isArrayParameterWithFiltering(k, v) {
|
|
243
|
+
const filterParameter = getFilterParameter(k);
|
|
244
|
+
if (settings.parameterSettings?.[k]?.type === 'stateful') {
|
|
245
|
+
const statefulSetting = settings.parameterSettings[k];
|
|
246
|
+
return statefulSetting.nestedSettings.type === 'array' &&
|
|
247
|
+
(filterParameter ?? true)
|
|
248
|
+
&& Array.isArray(v);
|
|
249
|
+
}
|
|
250
|
+
return settings.parameterSettings?.[k]?.type === 'array'
|
|
251
|
+
&& (filterParameter ?? true)
|
|
252
|
+
&& Array.isArray(v);
|
|
253
|
+
}
|
|
254
|
+
// For stateless mode, we must filter the current array so that the diff algorithm will not detect any deletes
|
|
255
|
+
function filterArrayParameterForStatelessMode(k, v) {
|
|
256
|
+
const desiredArray = desired[k];
|
|
257
|
+
const matcher = settings.parameterSettings[k].type === 'stateful'
|
|
258
|
+
? settings.parameterSettings[k]
|
|
259
|
+
.nestedSettings
|
|
260
|
+
.isElementEqual
|
|
261
|
+
: settings.parameterSettings[k]
|
|
262
|
+
.isElementEqual;
|
|
263
|
+
const desiredCopy = [...desiredArray];
|
|
264
|
+
const currentCopy = [...v];
|
|
265
|
+
const defaultFilterMethod = ((desired, current) => {
|
|
266
|
+
const result = [];
|
|
267
|
+
for (let counter = desired.length - 1; counter >= 0; counter--) {
|
|
268
|
+
const idx = currentCopy.findIndex((e2) => matcher(desired[counter], e2));
|
|
269
|
+
if (idx === -1) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
desired.splice(counter, 1);
|
|
273
|
+
const [element] = current.splice(idx, 1);
|
|
274
|
+
result.push(element);
|
|
275
|
+
}
|
|
276
|
+
return result;
|
|
277
|
+
});
|
|
278
|
+
const filterParameter = getFilterParameter(k);
|
|
279
|
+
return typeof filterParameter === 'function'
|
|
280
|
+
? filterParameter(desiredCopy, currentCopy)
|
|
281
|
+
: defaultFilterMethod(desiredCopy, currentCopy);
|
|
282
|
+
}
|
|
283
|
+
function filterArrayParameterForDeletes(k, v) {
|
|
284
|
+
const stateArray = state[k];
|
|
285
|
+
const matcher = settings.parameterSettings[k].type === 'stateful'
|
|
286
|
+
? settings.parameterSettings[k]
|
|
287
|
+
.nestedSettings
|
|
288
|
+
.isElementEqual
|
|
289
|
+
: settings.parameterSettings[k]
|
|
290
|
+
.isElementEqual;
|
|
291
|
+
const stateCopy = [...stateArray];
|
|
292
|
+
const currentCopy = [...v];
|
|
293
|
+
const defaultFilterMethod = ((state, current) => {
|
|
294
|
+
const result = [];
|
|
295
|
+
for (let counter = state.length - 1; counter >= 0; counter--) {
|
|
296
|
+
const idx = currentCopy.findIndex((e2) => matcher(state[counter], e2));
|
|
297
|
+
if (idx === -1) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
state.splice(counter, 1);
|
|
301
|
+
const [element] = current.splice(idx, 1);
|
|
302
|
+
result.push(element);
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
305
|
+
});
|
|
306
|
+
const filterParameter = getFilterParameter(k);
|
|
307
|
+
return typeof filterParameter === 'function'
|
|
308
|
+
? filterParameter(stateCopy, currentCopy)
|
|
309
|
+
: defaultFilterMethod(stateCopy, currentCopy);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// TODO: This needs to be revisited. I don't think this is valid anymore.
|
|
313
|
+
// 1. For all scenarios, there shouldn't be an apply without a plan beforehand
|
|
314
|
+
requiresChanges() {
|
|
315
|
+
return this.changeSet.operation !== ResourceOperation.NOOP;
|
|
316
|
+
}
|
|
317
|
+
/** Convert the plan to a JSON response object */
|
|
318
|
+
toResponse() {
|
|
319
|
+
return {
|
|
320
|
+
planId: this.id,
|
|
321
|
+
operation: this.changeSet.operation,
|
|
322
|
+
isStateful: this.isStateful,
|
|
323
|
+
resourceName: this.coreParameters.name,
|
|
324
|
+
resourceType: this.coreParameters.type,
|
|
325
|
+
parameters: this.changeSet.parameterChanges,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ApplyRequestData, GetResourceInfoRequestData, GetResourceInfoResponseData, ImportRequestData, ImportResponseData, InitializeRequestData, InitializeResponseData, MatchRequestData, MatchResponseData, PlanRequestData, PlanResponseData, ResourceConfig, ResourceJson, SetVerbosityRequestData, ValidateRequestData, ValidateResponseData } from 'codify-schemas';
|
|
2
|
+
import { Plan } from '../plan/plan.js';
|
|
3
|
+
import { BackgroundPty } from '../pty/background-pty.js';
|
|
4
|
+
import { Resource } from '../resource/resource.js';
|
|
5
|
+
import { ResourceController } from '../resource/resource-controller.js';
|
|
6
|
+
export declare class Plugin {
|
|
7
|
+
name: string;
|
|
8
|
+
resourceControllers: Map<string, ResourceController<ResourceConfig>>;
|
|
9
|
+
planStorage: Map<string, Plan<any>>;
|
|
10
|
+
planPty: BackgroundPty;
|
|
11
|
+
constructor(name: string, resourceControllers: Map<string, ResourceController<ResourceConfig>>);
|
|
12
|
+
static create(name: string, resources: Resource<any>[]): Plugin;
|
|
13
|
+
initialize(data: InitializeRequestData): Promise<InitializeResponseData>;
|
|
14
|
+
getResourceInfo(data: GetResourceInfoRequestData): GetResourceInfoResponseData;
|
|
15
|
+
match(data: MatchRequestData): Promise<MatchResponseData>;
|
|
16
|
+
import(data: ImportRequestData): Promise<ImportResponseData>;
|
|
17
|
+
validate(data: ValidateRequestData): Promise<ValidateResponseData>;
|
|
18
|
+
plan(data: PlanRequestData): Promise<PlanResponseData>;
|
|
19
|
+
apply(data: ApplyRequestData): Promise<void>;
|
|
20
|
+
setVerbosityLevel(data: SetVerbosityRequestData): Promise<void>;
|
|
21
|
+
kill(): Promise<void>;
|
|
22
|
+
private resolvePlan;
|
|
23
|
+
protected crossValidateResources(resources: ResourceJson[]): Promise<void>;
|
|
24
|
+
}
|