@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.
Files changed (152) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.json +30 -0
  3. package/.github/workflows/release.yaml +19 -0
  4. package/.github/workflows/unit-test-ci.yaml +18 -0
  5. package/.prettierrc.json +1 -0
  6. package/bin/build.js +189 -0
  7. package/dist/bin/build.d.ts +1 -0
  8. package/dist/bin/build.js +80 -0
  9. package/dist/bin/deploy-plugin.d.ts +2 -0
  10. package/dist/bin/deploy-plugin.js +8 -0
  11. package/dist/common/errors.d.ts +8 -0
  12. package/dist/common/errors.js +24 -0
  13. package/dist/entities/change-set.d.ts +24 -0
  14. package/dist/entities/change-set.js +152 -0
  15. package/dist/entities/errors.d.ts +4 -0
  16. package/dist/entities/errors.js +7 -0
  17. package/dist/entities/plan-types.d.ts +25 -0
  18. package/dist/entities/plan-types.js +1 -0
  19. package/dist/entities/plan.d.ts +15 -0
  20. package/dist/entities/plan.js +127 -0
  21. package/dist/entities/plugin.d.ts +16 -0
  22. package/dist/entities/plugin.js +80 -0
  23. package/dist/entities/resource-options.d.ts +31 -0
  24. package/dist/entities/resource-options.js +76 -0
  25. package/dist/entities/resource-types.d.ts +11 -0
  26. package/dist/entities/resource-types.js +1 -0
  27. package/dist/entities/resource.d.ts +42 -0
  28. package/dist/entities/resource.js +303 -0
  29. package/dist/entities/stateful-parameter.d.ts +29 -0
  30. package/dist/entities/stateful-parameter.js +46 -0
  31. package/dist/entities/transform-parameter.d.ts +4 -0
  32. package/dist/entities/transform-parameter.js +2 -0
  33. package/dist/errors.d.ts +4 -0
  34. package/dist/errors.js +7 -0
  35. package/dist/index.d.ts +20 -0
  36. package/dist/index.js +26 -0
  37. package/dist/messages/handlers.d.ts +14 -0
  38. package/dist/messages/handlers.js +134 -0
  39. package/dist/messages/sender.d.ts +11 -0
  40. package/dist/messages/sender.js +57 -0
  41. package/dist/plan/change-set.d.ts +53 -0
  42. package/dist/plan/change-set.js +153 -0
  43. package/dist/plan/plan-types.d.ts +23 -0
  44. package/dist/plan/plan-types.js +1 -0
  45. package/dist/plan/plan.d.ts +66 -0
  46. package/dist/plan/plan.js +328 -0
  47. package/dist/plugin/plugin.d.ts +24 -0
  48. package/dist/plugin/plugin.js +200 -0
  49. package/dist/pty/background-pty.d.ts +21 -0
  50. package/dist/pty/background-pty.js +127 -0
  51. package/dist/pty/index.d.ts +50 -0
  52. package/dist/pty/index.js +20 -0
  53. package/dist/pty/promise-queue.d.ts +5 -0
  54. package/dist/pty/promise-queue.js +26 -0
  55. package/dist/pty/seqeuntial-pty.d.ts +17 -0
  56. package/dist/pty/seqeuntial-pty.js +119 -0
  57. package/dist/pty/vitest.config.d.ts +2 -0
  58. package/dist/pty/vitest.config.js +11 -0
  59. package/dist/resource/config-parser.d.ts +11 -0
  60. package/dist/resource/config-parser.js +21 -0
  61. package/dist/resource/parsed-resource-settings.d.ts +47 -0
  62. package/dist/resource/parsed-resource-settings.js +196 -0
  63. package/dist/resource/resource-controller.d.ts +36 -0
  64. package/dist/resource/resource-controller.js +402 -0
  65. package/dist/resource/resource-settings.d.ts +303 -0
  66. package/dist/resource/resource-settings.js +147 -0
  67. package/dist/resource/resource.d.ts +144 -0
  68. package/dist/resource/resource.js +44 -0
  69. package/dist/resource/stateful-parameter.d.ts +165 -0
  70. package/dist/resource/stateful-parameter.js +94 -0
  71. package/dist/scripts/deploy.d.ts +1 -0
  72. package/dist/scripts/deploy.js +2 -0
  73. package/dist/stateful-parameter/stateful-parameter-controller.d.ts +21 -0
  74. package/dist/stateful-parameter/stateful-parameter-controller.js +81 -0
  75. package/dist/stateful-parameter/stateful-parameter.d.ts +144 -0
  76. package/dist/stateful-parameter/stateful-parameter.js +43 -0
  77. package/dist/test.d.ts +1 -0
  78. package/dist/test.js +5 -0
  79. package/dist/utils/codify-spawn.d.ts +29 -0
  80. package/dist/utils/codify-spawn.js +136 -0
  81. package/dist/utils/debug.d.ts +2 -0
  82. package/dist/utils/debug.js +10 -0
  83. package/dist/utils/file-utils.d.ts +23 -0
  84. package/dist/utils/file-utils.js +186 -0
  85. package/dist/utils/functions.d.ts +12 -0
  86. package/dist/utils/functions.js +74 -0
  87. package/dist/utils/index.d.ts +46 -0
  88. package/dist/utils/index.js +271 -0
  89. package/dist/utils/internal-utils.d.ts +12 -0
  90. package/dist/utils/internal-utils.js +74 -0
  91. package/dist/utils/load-resources.d.ts +1 -0
  92. package/dist/utils/load-resources.js +46 -0
  93. package/dist/utils/package-json-utils.d.ts +12 -0
  94. package/dist/utils/package-json-utils.js +34 -0
  95. package/dist/utils/pty-local-storage.d.ts +2 -0
  96. package/dist/utils/pty-local-storage.js +2 -0
  97. package/dist/utils/spawn-2.d.ts +5 -0
  98. package/dist/utils/spawn-2.js +7 -0
  99. package/dist/utils/spawn.d.ts +29 -0
  100. package/dist/utils/spawn.js +124 -0
  101. package/dist/utils/utils.d.ts +18 -0
  102. package/dist/utils/utils.js +86 -0
  103. package/dist/utils/verbosity-level.d.ts +5 -0
  104. package/dist/utils/verbosity-level.js +9 -0
  105. package/package.json +59 -0
  106. package/rollup.config.js +24 -0
  107. package/src/common/errors.test.ts +43 -0
  108. package/src/common/errors.ts +31 -0
  109. package/src/errors.ts +8 -0
  110. package/src/index.test.ts +6 -0
  111. package/src/index.ts +30 -0
  112. package/src/messages/handlers.test.ts +329 -0
  113. package/src/messages/handlers.ts +181 -0
  114. package/src/messages/sender.ts +69 -0
  115. package/src/plan/change-set.test.ts +280 -0
  116. package/src/plan/change-set.ts +236 -0
  117. package/src/plan/plan-types.ts +27 -0
  118. package/src/plan/plan.test.ts +413 -0
  119. package/src/plan/plan.ts +499 -0
  120. package/src/plugin/plugin.test.ts +533 -0
  121. package/src/plugin/plugin.ts +291 -0
  122. package/src/pty/background-pty.test.ts +69 -0
  123. package/src/pty/background-pty.ts +154 -0
  124. package/src/pty/index.test.ts +129 -0
  125. package/src/pty/index.ts +66 -0
  126. package/src/pty/promise-queue.ts +33 -0
  127. package/src/pty/seqeuntial-pty.ts +151 -0
  128. package/src/pty/sequential-pty.test.ts +194 -0
  129. package/src/resource/config-parser.ts +42 -0
  130. package/src/resource/parsed-resource-settings.test.ts +186 -0
  131. package/src/resource/parsed-resource-settings.ts +307 -0
  132. package/src/resource/resource-controller-stateful-mode.test.ts +253 -0
  133. package/src/resource/resource-controller.test.ts +1081 -0
  134. package/src/resource/resource-controller.ts +563 -0
  135. package/src/resource/resource-settings.test.ts +1213 -0
  136. package/src/resource/resource-settings.ts +545 -0
  137. package/src/resource/resource.ts +157 -0
  138. package/src/stateful-parameter/stateful-parameter-controller.test.ts +244 -0
  139. package/src/stateful-parameter/stateful-parameter-controller.ts +111 -0
  140. package/src/stateful-parameter/stateful-parameter.ts +160 -0
  141. package/src/utils/debug.ts +11 -0
  142. package/src/utils/file-utils.test.ts +7 -0
  143. package/src/utils/file-utils.ts +231 -0
  144. package/src/utils/functions.ts +103 -0
  145. package/src/utils/index.ts +340 -0
  146. package/src/utils/internal-utils.test.ts +52 -0
  147. package/src/utils/pty-local-storage.ts +3 -0
  148. package/src/utils/test-utils.test.ts +96 -0
  149. package/src/utils/verbosity-level.ts +11 -0
  150. package/tsconfig.json +26 -0
  151. package/tsconfig.test.json +9 -0
  152. package/vitest.config.ts +10 -0
@@ -0,0 +1,236 @@
1
+ import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
2
+
3
+ import { ParsedParameterSetting } from '../resource/parsed-resource-settings.js';
4
+ import { ResourceSettings } from '../resource/resource-settings.js';
5
+
6
+ /**
7
+ * A parameter change describes a parameter level change to a resource.
8
+ */
9
+ export interface ParameterChange<T extends StringIndexedObject> {
10
+ /**
11
+ * The name of the parameter
12
+ */
13
+ name: keyof T & string;
14
+
15
+ /**
16
+ * The operation to be performed on the parameter.
17
+ */
18
+ operation: ParameterOperation;
19
+
20
+ /**
21
+ * The previous value of the resource (the current value on the system)
22
+ */
23
+ previousValue: any | null;
24
+
25
+ /**
26
+ * The new value of the resource (the desired value)
27
+ */
28
+ newValue: any | null;
29
+
30
+ /**
31
+ * Whether the parameter is sensitive
32
+ */
33
+ isSensitive: boolean;
34
+ }
35
+
36
+ // Change set will coerce undefined values to null because undefined is not valid JSON
37
+ export class ChangeSet<T extends StringIndexedObject> {
38
+ operation: ResourceOperation
39
+ parameterChanges: Array<ParameterChange<T>>
40
+
41
+ constructor(
42
+ operation: ResourceOperation,
43
+ parameterChanges: Array<ParameterChange<T>>
44
+ ) {
45
+ this.operation = operation;
46
+ this.parameterChanges = parameterChanges;
47
+ }
48
+
49
+ get desiredParameters(): T {
50
+ return this.parameterChanges
51
+ .reduce((obj, pc) => ({
52
+ ...obj,
53
+ [pc.name]: pc.newValue,
54
+ }), {}) as T;
55
+ }
56
+
57
+ get currentParameters(): T {
58
+ return this.parameterChanges
59
+ .reduce((obj, pc) => ({
60
+ ...obj,
61
+ [pc.name]: pc.previousValue,
62
+ }), {}) as T;
63
+ }
64
+
65
+ static empty<T extends StringIndexedObject>(): ChangeSet<T> {
66
+ return new ChangeSet<T>(ResourceOperation.NOOP, []);
67
+ }
68
+
69
+ static create<T extends StringIndexedObject>(desired: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T> {
70
+ const parameterChanges = Object.entries(desired)
71
+ .map(([k, v]) => ({
72
+ name: k,
73
+ operation: ParameterOperation.ADD,
74
+ previousValue: null,
75
+ newValue: v ?? null,
76
+ isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
77
+ }))
78
+
79
+ return new ChangeSet(ResourceOperation.CREATE, parameterChanges);
80
+ }
81
+
82
+ static noop<T extends StringIndexedObject>(parameters: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T> {
83
+ const parameterChanges = Object.entries(parameters)
84
+ .map(([k, v]) => ({
85
+ name: k,
86
+ operation: ParameterOperation.NOOP,
87
+ previousValue: v ?? null,
88
+ newValue: v ?? null,
89
+ isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
90
+ }))
91
+
92
+ return new ChangeSet(ResourceOperation.NOOP, parameterChanges);
93
+ }
94
+
95
+ static destroy<T extends StringIndexedObject>(current: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T> {
96
+ const parameterChanges = Object.entries(current)
97
+ .map(([k, v]) => ({
98
+ name: k,
99
+ operation: ParameterOperation.REMOVE,
100
+ previousValue: v ?? null,
101
+ newValue: null,
102
+ isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
103
+ }))
104
+
105
+ return new ChangeSet(ResourceOperation.DESTROY, parameterChanges);
106
+ }
107
+
108
+ static calculateModification<T extends StringIndexedObject>(
109
+ desired: Partial<T>,
110
+ current: Partial<T>,
111
+ parameterSettings: Partial<Record<keyof T, ParsedParameterSetting>> = {},
112
+ ): ChangeSet<T> {
113
+ const pc = ChangeSet.calculateParameterChanges(desired, current, parameterSettings);
114
+
115
+ const statefulParameterKeys = new Set(
116
+ Object.entries(parameterSettings)
117
+ .filter(([, v]) => v?.type === 'stateful')
118
+ .map(([k]) => k)
119
+ )
120
+
121
+ const resourceOperation = pc
122
+ .filter((change) => change.operation !== ParameterOperation.NOOP)
123
+ .reduce((operation: ResourceOperation, curr: ParameterChange<T>) => {
124
+ let newOperation: ResourceOperation;
125
+ if (statefulParameterKeys.has(curr.name)) {
126
+ newOperation = ResourceOperation.MODIFY // All stateful parameters are modify only
127
+ } else if (parameterSettings[curr.name]?.canModify) {
128
+ newOperation = ResourceOperation.MODIFY
129
+ } else {
130
+ newOperation = ResourceOperation.RECREATE; // Default to Re-create. Should handle the majority of use cases
131
+ }
132
+
133
+ return ChangeSet.combineResourceOperations(operation, newOperation);
134
+ }, ResourceOperation.NOOP);
135
+
136
+ return new ChangeSet<T>(resourceOperation, pc);
137
+ }
138
+
139
+ /**
140
+ * Calculates the differences between the desired and current parameters,
141
+ * and returns a list of parameter changes that describe what needs to be added,
142
+ * removed, or modified to match the desired state.
143
+ *
144
+ * @param {Partial<T>} desiredParameters - The desired target state of the parameters.
145
+ * @param {Partial<T>} currentParameters - The current state of the parameters.
146
+ * @param {Partial<Record<keyof T, ParameterSetting>>} [parameterOptions] - Optional settings used when comparing parameters.
147
+ * @return {ParameterChange<T>[]} A list of changes required to transition from the current state to the desired state.
148
+ */
149
+ private static calculateParameterChanges<T extends StringIndexedObject>(
150
+ desiredParameters: Partial<T>,
151
+ currentParameters: Partial<T>,
152
+ parameterOptions?: Partial<Record<keyof T, ParsedParameterSetting>>,
153
+ ): ParameterChange<T>[] {
154
+ const parameterChangeSet = new Array<ParameterChange<T>>();
155
+
156
+ // Filter out null and undefined values or else the diff below will not work
157
+ const desired = Object.fromEntries(
158
+ Object.entries(desiredParameters).filter(([, v]) => v !== null && v !== undefined)
159
+ ) as Partial<T>
160
+
161
+ const current = Object.fromEntries(
162
+ Object.entries(currentParameters).filter(([, v]) => v !== null && v !== undefined)
163
+ ) as Partial<T>
164
+
165
+ for (const k of new Set([...Object.keys(current), ...Object.keys(desired)])) {
166
+ if (ChangeSet.isSame(desired[k], current[k], parameterOptions?.[k])) {
167
+ parameterChangeSet.push({
168
+ name: k,
169
+ previousValue: current[k] ?? null,
170
+ newValue: desired[k] ?? null,
171
+ operation: ParameterOperation.NOOP,
172
+ isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
173
+ })
174
+
175
+ continue;
176
+ }
177
+
178
+ if ((desired?.[k] === null || desired?.[k] === undefined) && (current?.[k] !== null && current?.[k] !== undefined)) {
179
+ parameterChangeSet.push({
180
+ name: k,
181
+ previousValue: current[k] ?? null,
182
+ newValue: null,
183
+ operation: ParameterOperation.REMOVE,
184
+ isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
185
+ })
186
+
187
+ continue;
188
+ }
189
+
190
+ if ((current?.[k] === null || current?.[k] === undefined) && (desired?.[k] !== null && desired?.[k] !== undefined)) {
191
+ parameterChangeSet.push({
192
+ name: k,
193
+ previousValue: null,
194
+ newValue: desired[k] ?? null,
195
+ operation: ParameterOperation.ADD,
196
+ isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
197
+ })
198
+
199
+ continue;
200
+ }
201
+
202
+ parameterChangeSet.push({
203
+ name: k,
204
+ previousValue: current[k] ?? null,
205
+ newValue: desired[k] ?? null,
206
+ operation: ParameterOperation.MODIFY,
207
+ isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
208
+ })
209
+ }
210
+
211
+ return parameterChangeSet;
212
+ }
213
+
214
+ private static combineResourceOperations(prev: ResourceOperation, next: ResourceOperation) {
215
+ const orderOfOperations = [
216
+ ResourceOperation.NOOP,
217
+ ResourceOperation.MODIFY,
218
+ ResourceOperation.RECREATE,
219
+ ResourceOperation.CREATE,
220
+ ResourceOperation.DESTROY,
221
+ ]
222
+
223
+ const indexPrev = orderOfOperations.indexOf(prev);
224
+ const indexNext = orderOfOperations.indexOf(next);
225
+
226
+ return orderOfOperations[Math.max(indexPrev, indexNext)];
227
+ }
228
+
229
+ private static isSame(
230
+ desired: unknown,
231
+ current: unknown,
232
+ setting?: ParsedParameterSetting,
233
+ ): boolean {
234
+ return (setting?.isEqual ?? ((a: unknown, b: unknown) => a === b))(desired, current)
235
+ }
236
+ }
@@ -0,0 +1,27 @@
1
+ import { StringIndexedObject } from 'codify-schemas';
2
+
3
+ import { Plan } from './plan.js';
4
+
5
+ /**
6
+ * A narrower type for plans for CREATE operations. Only desiredConfig is not null.
7
+ */
8
+ export interface CreatePlan<T extends StringIndexedObject> extends Plan<T> {
9
+ desiredConfig: T;
10
+ currentConfig: null;
11
+ }
12
+
13
+ /**
14
+ * A narrower type for plans for DESTROY operations. Only currentConfig is not null.
15
+ */
16
+ export interface DestroyPlan<T extends StringIndexedObject> extends Plan<T> {
17
+ desiredConfig: null;
18
+ currentConfig: T;
19
+ }
20
+
21
+ /**
22
+ * A narrower type for plans for MODIFY and RE-CREATE operations.
23
+ */
24
+ export interface ModifyPlan<T extends StringIndexedObject> extends Plan<T> {
25
+ desiredConfig: T;
26
+ currentConfig: T;
27
+ }
@@ -0,0 +1,413 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Plan } from './plan.js';
3
+ import { OS, ParameterOperation, ResourceOperation } from 'codify-schemas';
4
+ import { TestConfig, TestResource } from '../utils/test-utils.test.js';
5
+ import { ResourceController } from '../resource/resource-controller.js';
6
+ import { ParsedResourceSettings } from '../resource/parsed-resource-settings.js';
7
+ import { ResourceSettings } from '../resource/resource-settings.js';
8
+
9
+ describe('Plan entity tests', () => {
10
+ it('Adds default values properly when plan is parsed from request (Create)', () => {
11
+ const resource = createTestResource()
12
+ const controller = new ResourceController(resource);
13
+
14
+ const plan = Plan.fromResponse({
15
+ operation: ResourceOperation.CREATE,
16
+ resourceType: 'type',
17
+ parameters: [{
18
+ name: 'propB',
19
+ operation: ParameterOperation.ADD,
20
+ previousValue: null,
21
+ newValue: 'propBValue'
22
+ }],
23
+ isStateful: false,
24
+ }, controller.parsedSettings.defaultValues);
25
+
26
+ expect(plan.currentConfig).to.be.null;
27
+
28
+ expect(plan.desiredConfig).toMatchObject({
29
+ propA: 'defaultA',
30
+ propB: 'propBValue',
31
+ })
32
+
33
+ expect(plan.changeSet.parameterChanges
34
+ .every((pc) => pc.operation === ParameterOperation.ADD)
35
+ ).to.be.true;
36
+ })
37
+
38
+ it('Adds default values properly when plan is parsed from request (Destroy)', () => {
39
+ const resource = createTestResource()
40
+ const controller = new ResourceController(resource);
41
+
42
+ const plan = Plan.fromResponse({
43
+ operation: ResourceOperation.DESTROY,
44
+ resourceType: 'type',
45
+ parameters: [{
46
+ name: 'propB',
47
+ operation: ParameterOperation.REMOVE,
48
+ previousValue: 'propBValue',
49
+ newValue: null,
50
+ }],
51
+ isStateful: false,
52
+ }, controller.parsedSettings.defaultValues);
53
+
54
+ expect(plan.currentConfig).toMatchObject({
55
+ propA: 'defaultA',
56
+ propB: 'propBValue',
57
+ })
58
+
59
+ expect(plan.desiredConfig).to.be.null;
60
+
61
+ expect(plan.changeSet.parameterChanges
62
+ .every((pc) => pc.operation === ParameterOperation.REMOVE)
63
+ ).to.be.true;
64
+ })
65
+
66
+ it('Adds default values properly when plan is parsed from request (No-op)', () => {
67
+ const resource = createTestResource()
68
+ const controller = new ResourceController(resource);
69
+
70
+ const plan = Plan.fromResponse({
71
+ operation: ResourceOperation.NOOP,
72
+ resourceType: 'type',
73
+ parameters: [{
74
+ name: 'propB',
75
+ operation: ParameterOperation.NOOP,
76
+ previousValue: 'propBValue',
77
+ newValue: 'propBValue',
78
+ }],
79
+ isStateful: false,
80
+ }, controller.parsedSettings.defaultValues);
81
+
82
+ expect(plan.currentConfig).toMatchObject({
83
+ propA: 'defaultA',
84
+ propB: 'propBValue',
85
+ })
86
+
87
+ expect(plan.desiredConfig).toMatchObject({
88
+ propA: 'defaultA',
89
+ propB: 'propBValue',
90
+ })
91
+
92
+ expect(plan.changeSet.parameterChanges
93
+ .every((pc) => pc.operation === ParameterOperation.NOOP)
94
+ ).to.be.true;
95
+ })
96
+
97
+ it('Does not add default value if a value has already been specified', () => {
98
+ const resource = createTestResource()
99
+ const controller = new ResourceController(resource);
100
+
101
+ const plan = Plan.fromResponse({
102
+ operation: ResourceOperation.CREATE,
103
+ resourceType: 'type',
104
+ parameters: [{
105
+ name: 'propB',
106
+ operation: ParameterOperation.ADD,
107
+ previousValue: null,
108
+ newValue: 'propBValue',
109
+ }, {
110
+ name: 'propA',
111
+ operation: ParameterOperation.ADD,
112
+ previousValue: null,
113
+ newValue: 'propAValue',
114
+ }],
115
+ isStateful: false,
116
+ }, controller.parsedSettings.defaultValues);
117
+
118
+ expect(plan.currentConfig).to.be.null
119
+
120
+ expect(plan.desiredConfig).toMatchObject({
121
+ propA: 'propAValue',
122
+ propB: 'propBValue',
123
+ })
124
+
125
+ expect(plan.changeSet.parameterChanges
126
+ .every((pc) => pc.operation === ParameterOperation.ADD)
127
+ ).to.be.true;
128
+ })
129
+
130
+ it('Returns the original resource names', () => {
131
+ const plan = Plan.calculate<TestConfig>({
132
+ desired: { propA: 'propA' },
133
+ currentArray: [{ propA: 'propA2' }],
134
+ state: null,
135
+ core: {
136
+ type: 'type',
137
+ name: 'name1'
138
+ },
139
+ settings: new ParsedResourceSettings<TestConfig>({ id: 'type' }),
140
+ isStateful: false,
141
+ });
142
+
143
+ expect(plan.toResponse()).toMatchObject({
144
+ resourceType: 'type',
145
+ resourceName: 'name1',
146
+ operation: ResourceOperation.RECREATE
147
+ })
148
+ })
149
+
150
+ it('Filters array parameters in stateless mode (by default)', async () => {
151
+ const resource = new class extends TestResource {
152
+ getSettings(): ResourceSettings<any> {
153
+ return {
154
+ id: 'type',
155
+ operatingSystems: [OS.Darwin],
156
+ parameterSettings: {
157
+ propZ: { type: 'array', isElementEqual: (a, b) => b.includes(a) }
158
+ }
159
+ }
160
+ }
161
+
162
+ async refresh(): Promise<Partial<any> | null> {
163
+ return {
164
+ propZ: [
165
+ '20.15.0',
166
+ '20.15.1'
167
+ ]
168
+ }
169
+ }
170
+ }
171
+
172
+ const controller = new ResourceController(resource);
173
+ const plan = await controller.plan(
174
+ { type: 'type' },
175
+ { propZ: ['20.15'], } as any,
176
+ null,
177
+ false
178
+ )
179
+
180
+ expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
181
+ })
182
+
183
+ it('Filters array parameters in delete mode (when desired is null)', async () => {
184
+ const resource = new class extends TestResource {
185
+ getSettings(): ResourceSettings<any> {
186
+ return {
187
+ id: 'type',
188
+ operatingSystems: [OS.Darwin],
189
+ parameterSettings: {
190
+ propZ: { type: 'array', isElementEqual: (a, b) => b.includes(a) }
191
+ }
192
+ }
193
+ }
194
+
195
+ async refresh(): Promise<Partial<any> | null> {
196
+ return {
197
+ propZ: [
198
+ '20.15.0',
199
+ '20.15.1'
200
+ ]
201
+ }
202
+ }
203
+ }
204
+
205
+ const controller = new ResourceController(resource);
206
+ const plan = await controller.plan(
207
+ { type: 'type' },
208
+ null,
209
+ { propZ: ['20.15.0'], } as any,
210
+ true
211
+ )
212
+
213
+ console.log(JSON.stringify(plan, null, 2));
214
+ expect(plan).toMatchObject({
215
+ id: expect.any(String),
216
+ changeSet: expect.objectContaining({
217
+ operation: ResourceOperation.DESTROY,
218
+ parameterChanges: [
219
+ expect.objectContaining({ operation: 'remove', name: 'propZ', previousValue: ['20.15.0'], newValue: null }),
220
+ ],
221
+ }),
222
+ coreParameters: expect.objectContaining({
223
+ type: 'type',
224
+ }),
225
+ isStateful: true,
226
+ })
227
+ })
228
+
229
+ it('Doesn\'t filters array parameters if filtering is disabled', async () => {
230
+ const resource = new class extends TestResource {
231
+ getSettings(): ResourceSettings<any> {
232
+ return {
233
+ id: 'type',
234
+ operatingSystems: [OS.Darwin],
235
+ parameterSettings: {
236
+ propZ: {
237
+ type: 'array',
238
+ canModify: true,
239
+ isElementEqual: (a, b) => b.includes(a),
240
+ filterInStatelessMode: false
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ async refresh(): Promise<Partial<any> | null> {
247
+ return {
248
+ propZ: [
249
+ '20.15.0',
250
+ '20.15.1'
251
+ ]
252
+ }
253
+ }
254
+ }
255
+
256
+ const controller = new ResourceController(resource);
257
+ const plan = await controller.plan(
258
+ { type: 'type' },
259
+ { propZ: ['20.15'], } as any,
260
+ null,
261
+ false
262
+ )
263
+
264
+ expect(plan.changeSet).toMatchObject({
265
+ operation: ResourceOperation.MODIFY,
266
+ parameterChanges: expect.arrayContaining([
267
+ expect.objectContaining({
268
+ name: 'propZ',
269
+ previousValue: expect.arrayContaining([
270
+ '20.15.0',
271
+ '20.15.1'
272
+ ]),
273
+ newValue: expect.arrayContaining([
274
+ '20.15'
275
+ ]),
276
+ operation: 'modify'
277
+ })
278
+ ])
279
+ })
280
+ })
281
+
282
+ it('Can use the requiredParameters to match the correct resources together', async () => {
283
+ const resource1 = new class extends TestResource {
284
+ getSettings(): ResourceSettings<TestConfig> {
285
+ return {
286
+ id: 'type',
287
+ operatingSystems: [OS.Darwin],
288
+ parameterSettings: {
289
+ propA: { type: 'string' },
290
+ propB: { type: 'string', canModify: true },
291
+ },
292
+ allowMultiple: {
293
+ identifyingParameters: ['propA']
294
+ }
295
+ }
296
+ }
297
+
298
+ async refresh(): Promise<Partial<any> | null> {
299
+ return [{
300
+ propA: 'same',
301
+ propB: 'old',
302
+ }, {
303
+ propA: 'different',
304
+ propB: 'different',
305
+ }]
306
+ }
307
+ }
308
+
309
+ const controller = new ResourceController(resource1);
310
+ const plan = await controller.plan(
311
+ { type: 'type' },
312
+ { propA: 'same', propB: 'new' },
313
+ null,
314
+ false
315
+ )
316
+
317
+ expect(plan.changeSet).toMatchObject({
318
+ operation: ResourceOperation.MODIFY,
319
+ parameterChanges: expect.arrayContaining([
320
+ expect.objectContaining({
321
+ name: 'propA',
322
+ previousValue: 'same',
323
+ newValue: 'same',
324
+ operation: 'noop'
325
+ }),
326
+ expect.objectContaining({
327
+ name: 'propB',
328
+ previousValue: 'old',
329
+ newValue: 'new',
330
+ operation: 'modify'
331
+ })
332
+ ])
333
+ })
334
+ })
335
+
336
+ it('Can use the schema to determine required parameters for multiple allowed', async () => {
337
+ const resource1 = new class extends TestResource {
338
+ getSettings(): ResourceSettings<TestConfig> {
339
+ return {
340
+ id: 'type',
341
+ operatingSystems: [OS.Darwin],
342
+ parameterSettings: {
343
+ propA: { type: 'string' },
344
+ propB: { type: 'string', canModify: true },
345
+ },
346
+ allowMultiple: true,
347
+ schema: {
348
+ '$schema': 'http://json-schema.org/draft-07/schema',
349
+ '$id': 'https://www.codifycli.com/type.json',
350
+ 'type': 'object',
351
+ 'properties': {
352
+ propA: { type: 'string' },
353
+ propB: { type: 'string' }
354
+ },
355
+ required: ['propA']
356
+ }
357
+ }
358
+ }
359
+
360
+ async refresh(): Promise<Partial<any> | null> {
361
+ return [{
362
+ propA: 'same',
363
+ propB: 'old',
364
+ }, {
365
+ propA: 'different',
366
+ propB: 'different',
367
+ }]
368
+ }
369
+ }
370
+
371
+ const controller = new ResourceController(resource1);
372
+ const plan = await controller.plan(
373
+ { type: 'type' },
374
+ { propA: 'same', propB: 'new' },
375
+ null,
376
+ false
377
+ )
378
+
379
+ expect(plan.changeSet).toMatchObject({
380
+ operation: ResourceOperation.MODIFY,
381
+ parameterChanges: expect.arrayContaining([
382
+ expect.objectContaining({
383
+ name: 'propA',
384
+ previousValue: 'same',
385
+ newValue: 'same',
386
+ operation: 'noop'
387
+ }),
388
+ expect.objectContaining({
389
+ name: 'propB',
390
+ previousValue: 'old',
391
+ newValue: 'new',
392
+ operation: 'modify'
393
+ })
394
+ ])
395
+ })
396
+ })
397
+ })
398
+
399
+ function createTestResource() {
400
+ return new class extends TestResource {
401
+ getSettings(): ResourceSettings<TestConfig> {
402
+ return {
403
+ id: 'type',
404
+ operatingSystems: [OS.Darwin],
405
+ parameterSettings: {
406
+ propA: {
407
+ default: 'defaultA'
408
+ }
409
+ }
410
+ }
411
+ }
412
+ };
413
+ }