@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,1213 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Plan } from '../plan/plan.js';
|
|
3
|
+
import { spy } from 'sinon';
|
|
4
|
+
import { OS, ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
5
|
+
import {
|
|
6
|
+
TestArrayStatefulParameter,
|
|
7
|
+
TestConfig,
|
|
8
|
+
testPlan,
|
|
9
|
+
TestResource,
|
|
10
|
+
TestStatefulParameter
|
|
11
|
+
} from '../utils/test-utils.test.js';
|
|
12
|
+
import { ArrayParameterSetting, ParameterSetting, ResourceSettings } from './resource-settings.js';
|
|
13
|
+
import { ResourceController } from './resource-controller.js';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
|
|
18
|
+
describe('Resource parameter tests', () => {
|
|
19
|
+
it('Generates a resource plan that includes stateful parameters (create)', async () => {
|
|
20
|
+
const statefulParameter = spy(new class extends TestStatefulParameter {
|
|
21
|
+
async refresh(): Promise<string | null> {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const resource = new class extends TestResource {
|
|
27
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
28
|
+
return {
|
|
29
|
+
id: 'type',
|
|
30
|
+
operatingSystems: [OS.Darwin],
|
|
31
|
+
parameterSettings: {
|
|
32
|
+
propA: { type: 'stateful', definition: statefulParameter }
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async refresh(): Promise<any> {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const controller = new ResourceController(resource);
|
|
43
|
+
const plan = await controller.plan(
|
|
44
|
+
{ type: 'type' },
|
|
45
|
+
{
|
|
46
|
+
propA: 'a',
|
|
47
|
+
propB: 10
|
|
48
|
+
},
|
|
49
|
+
null,
|
|
50
|
+
false
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
expect(statefulParameter.refresh.notCalled).to.be.true;
|
|
54
|
+
expect(plan.currentConfig).to.be.null;
|
|
55
|
+
expect(plan.desiredConfig).toMatchObject({
|
|
56
|
+
propA: 'a',
|
|
57
|
+
propB: 10
|
|
58
|
+
})
|
|
59
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.CREATE);
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('supports the creation of stateful parameters', async () => {
|
|
63
|
+
|
|
64
|
+
const statefulParameter = new class extends TestStatefulParameter {
|
|
65
|
+
async refresh(): Promise<string | null> {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const statefulParameterSpy = spy(statefulParameter);
|
|
71
|
+
|
|
72
|
+
const resource = new class extends TestResource {
|
|
73
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
74
|
+
return {
|
|
75
|
+
id: 'type',
|
|
76
|
+
operatingSystems: [OS.Darwin],
|
|
77
|
+
parameterSettings: {
|
|
78
|
+
propA: { type: 'stateful', definition: statefulParameterSpy }
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const controller = new ResourceController(resource);
|
|
85
|
+
const resourceSpy = spy(resource);
|
|
86
|
+
|
|
87
|
+
await controller.apply(
|
|
88
|
+
testPlan<TestConfig>({
|
|
89
|
+
desired: { propA: 'a', propB: 0, propC: 'c' }
|
|
90
|
+
})
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(statefulParameterSpy.add.calledOnce).to.be.true;
|
|
94
|
+
expect(resourceSpy.create.calledOnce).to.be.true;
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('supports the modification of stateful parameters', async () => {
|
|
98
|
+
const statefulParameter = new class extends TestStatefulParameter {
|
|
99
|
+
async refresh(): Promise<string | null> {
|
|
100
|
+
return 'b';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const statefulParameterSpy = spy(statefulParameter);
|
|
105
|
+
|
|
106
|
+
const resource = new class extends TestResource {
|
|
107
|
+
|
|
108
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
109
|
+
return {
|
|
110
|
+
id: 'type',
|
|
111
|
+
operatingSystems: [OS.Darwin],
|
|
112
|
+
parameterSettings: {
|
|
113
|
+
propA: { type: 'stateful', definition: statefulParameterSpy },
|
|
114
|
+
propB: { canModify: true },
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
120
|
+
return { propB: -1, propC: 'b' }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const controller = new ResourceController(resource);
|
|
125
|
+
|
|
126
|
+
const plan = await controller.plan(
|
|
127
|
+
{ type: 'type' },
|
|
128
|
+
{ propA: 'a', propB: 0, propC: 'b' },
|
|
129
|
+
null,
|
|
130
|
+
false
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
const resourceSpy = spy(resource);
|
|
134
|
+
await controller.apply(plan);
|
|
135
|
+
|
|
136
|
+
expect(statefulParameterSpy.modify.calledOnce).to.be.true;
|
|
137
|
+
expect(resourceSpy.modify.calledOnce).to.be.true;
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('Allows stateful parameters to have default values', async () => {
|
|
141
|
+
const statefulParameter = spy(new class extends TestStatefulParameter {
|
|
142
|
+
getSettings(): ParameterSetting {
|
|
143
|
+
return {
|
|
144
|
+
default: 'abc',
|
|
145
|
+
operatingSystems: [OS.Darwin],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async refresh(): Promise<string | null> {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const resource = new class extends TestResource {
|
|
155
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
156
|
+
return {
|
|
157
|
+
id: 'type',
|
|
158
|
+
operatingSystems: [OS.Darwin],
|
|
159
|
+
parameterSettings: {
|
|
160
|
+
propA: { type: 'stateful', definition: statefulParameter }
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async refresh(): Promise<any> {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const controller = new ResourceController(resource);
|
|
171
|
+
const plan = await controller.plan({
|
|
172
|
+
type: 'type',
|
|
173
|
+
}, {}, null, false)
|
|
174
|
+
|
|
175
|
+
expect(statefulParameter.refresh.notCalled).to.be.true;
|
|
176
|
+
expect(plan.currentConfig).to.be.null;
|
|
177
|
+
expect(plan.desiredConfig).toMatchObject({
|
|
178
|
+
propA: 'abc',
|
|
179
|
+
})
|
|
180
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.CREATE);
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('Filters array results in stateless mode to prevent modify from being called', async () => {
|
|
184
|
+
const statefulParameter = new class extends TestStatefulParameter {
|
|
185
|
+
getSettings(): ParameterSetting {
|
|
186
|
+
return { type: 'array' }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async refresh(): Promise<any | null> {
|
|
190
|
+
return ['a', 'b', 'c', 'd']
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const statefulParameterSpy = spy(statefulParameter);
|
|
195
|
+
|
|
196
|
+
const resource = new class extends TestResource {
|
|
197
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
198
|
+
return {
|
|
199
|
+
id: 'type',
|
|
200
|
+
operatingSystems: [OS.Darwin],
|
|
201
|
+
parameterSettings: {
|
|
202
|
+
propA: { type: 'stateful', definition: statefulParameterSpy },
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
208
|
+
return {};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const controller = new ResourceController(resource);
|
|
213
|
+
const plan = await controller.plan({ type: 'type' }, { propA: ['a', 'b'] } as any, null, false)
|
|
214
|
+
|
|
215
|
+
expect(plan).toMatchObject({
|
|
216
|
+
changeSet: {
|
|
217
|
+
operation: ResourceOperation.NOOP,
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('Filters array results in stateless mode to prevent modify from being called 2', async () => {
|
|
223
|
+
const statefulParameter = new class extends TestStatefulParameter {
|
|
224
|
+
async refresh(): Promise<any | null> {
|
|
225
|
+
return ['a', 'b']
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const statefulParameterSpy = spy(statefulParameter);
|
|
230
|
+
|
|
231
|
+
const resource = new class extends TestResource {
|
|
232
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
233
|
+
return {
|
|
234
|
+
id: 'type',
|
|
235
|
+
operatingSystems: [OS.Darwin],
|
|
236
|
+
parameterSettings: {
|
|
237
|
+
propA: { type: 'stateful', definition: statefulParameterSpy }
|
|
238
|
+
},
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
243
|
+
return {};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const controller = new ResourceController(resource);
|
|
248
|
+
const plan = await controller.plan({ type: 'type' }, { propA: ['a', 'b', 'c', 'd'] } as any, null, false)
|
|
249
|
+
|
|
250
|
+
expect(plan).toMatchObject({
|
|
251
|
+
changeSet: {
|
|
252
|
+
operation: ResourceOperation.MODIFY,
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('Can accept a custom filter function to filter in stateless mode', async () => {
|
|
258
|
+
const resource = new class extends TestResource {
|
|
259
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
260
|
+
return {
|
|
261
|
+
id: 'type',
|
|
262
|
+
operatingSystems: [OS.Darwin],
|
|
263
|
+
parameterSettings: {
|
|
264
|
+
hosts: {
|
|
265
|
+
type: 'array',
|
|
266
|
+
isElementEqual: 'object',
|
|
267
|
+
filterInStatelessMode: (desired, current) => {
|
|
268
|
+
return current.filter((d) => desired.some((c) => d.Host === c.Host))
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
276
|
+
return {
|
|
277
|
+
hosts: [
|
|
278
|
+
{
|
|
279
|
+
Host: '*',
|
|
280
|
+
AddKeysToAgent: 'yes',
|
|
281
|
+
IdentityFile: 'id_ed25519'
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
Host: 'github.com',
|
|
285
|
+
AddKeysToAgent: 'yes',
|
|
286
|
+
UseKeychain: 'yes',
|
|
287
|
+
IgnoreUnknown: 'UseKeychain',
|
|
288
|
+
IdentityFile: '~/.ssh/id_ed25519',
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const controller = new ResourceController(resource);
|
|
296
|
+
const plan = await controller.plan(
|
|
297
|
+
{ type: 'type' },
|
|
298
|
+
{
|
|
299
|
+
hosts: [
|
|
300
|
+
{
|
|
301
|
+
Host: 'new.com',
|
|
302
|
+
AddKeysToAgent: 'yes',
|
|
303
|
+
IdentityFile: '~/.ssh/id_ed25519'
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
Host: 'github.com',
|
|
307
|
+
AddKeysToAgent: 'yes',
|
|
308
|
+
UseKeychain: 'yes',
|
|
309
|
+
}
|
|
310
|
+
]
|
|
311
|
+
},
|
|
312
|
+
null,
|
|
313
|
+
false
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
expect(plan).toMatchObject({
|
|
317
|
+
'changeSet': {
|
|
318
|
+
'operation': 'recreate',
|
|
319
|
+
'parameterChanges': [
|
|
320
|
+
{
|
|
321
|
+
'name': 'hosts',
|
|
322
|
+
'previousValue': [
|
|
323
|
+
{
|
|
324
|
+
'Host': 'github.com',
|
|
325
|
+
'AddKeysToAgent': 'yes',
|
|
326
|
+
'UseKeychain': 'yes',
|
|
327
|
+
'IgnoreUnknown': 'UseKeychain',
|
|
328
|
+
'IdentityFile': '~/.ssh/id_ed25519'
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
'newValue': [
|
|
332
|
+
{
|
|
333
|
+
'Host': 'new.com',
|
|
334
|
+
'AddKeysToAgent': 'yes',
|
|
335
|
+
'IdentityFile': '~/.ssh/id_ed25519'
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
'Host': 'github.com',
|
|
339
|
+
'AddKeysToAgent': 'yes',
|
|
340
|
+
'UseKeychain': 'yes'
|
|
341
|
+
}
|
|
342
|
+
],
|
|
343
|
+
'operation': 'modify'
|
|
344
|
+
}
|
|
345
|
+
]
|
|
346
|
+
},
|
|
347
|
+
})
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('Uses isElementEqual for stateless mode filtering if available', async () => {
|
|
351
|
+
const statefulParameter = new class extends TestArrayStatefulParameter {
|
|
352
|
+
getSettings(): ArrayParameterSetting {
|
|
353
|
+
return {
|
|
354
|
+
type: 'array',
|
|
355
|
+
isElementEqual: (desired, current) => {
|
|
356
|
+
return current.includes(desired)
|
|
357
|
+
},
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async refresh(): Promise<any | null> {
|
|
362
|
+
return ['3.11.9']
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const statefulParameterSpy = spy(statefulParameter);
|
|
367
|
+
|
|
368
|
+
const resource = new class extends TestResource {
|
|
369
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
370
|
+
return {
|
|
371
|
+
id: 'type',
|
|
372
|
+
operatingSystems: [OS.Darwin],
|
|
373
|
+
parameterSettings: {
|
|
374
|
+
propA: { type: 'stateful', definition: statefulParameterSpy }
|
|
375
|
+
},
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
380
|
+
return {};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const controller = new ResourceController(resource);
|
|
385
|
+
const plan = await controller.plan({ type: 'type' }, { propA: ['3.11'] } as any, null, false)
|
|
386
|
+
|
|
387
|
+
expect(plan).toMatchObject({
|
|
388
|
+
changeSet: {
|
|
389
|
+
operation: ResourceOperation.NOOP,
|
|
390
|
+
}
|
|
391
|
+
})
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
it('Plans stateful parameters in the order specified', async () => {
|
|
395
|
+
const statefulParameterA = spy(new class extends TestStatefulParameter {
|
|
396
|
+
async refresh(): Promise<any | null> {
|
|
397
|
+
return performance.now()
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const statefulParameterB = spy(new class extends TestStatefulParameter {
|
|
402
|
+
async refresh(): Promise<any | null> {
|
|
403
|
+
return performance.now()
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const statefulParameterC = spy(new class extends TestStatefulParameter {
|
|
408
|
+
async refresh(): Promise<any | null> {
|
|
409
|
+
return performance.now()
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const statefulParameterD = spy(new class extends TestStatefulParameter {
|
|
414
|
+
async refresh(): Promise<any | null> {
|
|
415
|
+
return performance.now()
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const statefulParameterE = spy(new class extends TestStatefulParameter {
|
|
420
|
+
async refresh(): Promise<any | null> {
|
|
421
|
+
return performance.now()
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const resource = spy(new class extends TestResource {
|
|
426
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
427
|
+
return {
|
|
428
|
+
id: 'resourceType',
|
|
429
|
+
operatingSystems: [OS.Darwin],
|
|
430
|
+
parameterSettings: {
|
|
431
|
+
propA: { type: 'stateful', definition: statefulParameterA, order: 3 },
|
|
432
|
+
propB: { type: 'stateful', definition: statefulParameterB, order: 1 },
|
|
433
|
+
propC: { type: 'stateful', definition: statefulParameterC, order: 2 },
|
|
434
|
+
propD: { type: 'stateful', definition: statefulParameterD },
|
|
435
|
+
propE: { type: 'stateful', definition: statefulParameterE }
|
|
436
|
+
},
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
441
|
+
return {};
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const controller = new ResourceController(resource)
|
|
446
|
+
const plan = await controller.plan(
|
|
447
|
+
{ type: 'resourceType' },
|
|
448
|
+
{
|
|
449
|
+
propA: 'propA',
|
|
450
|
+
propB: 10,
|
|
451
|
+
propC: 'propC',
|
|
452
|
+
propD: 'propD',
|
|
453
|
+
propE: 'propE',
|
|
454
|
+
}, null,
|
|
455
|
+
false
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
expect(plan.currentConfig?.propB).to.be.lessThan(plan.currentConfig?.propC as any);
|
|
459
|
+
expect(plan.currentConfig?.propC).to.be.lessThan(plan.currentConfig?.propA as any);
|
|
460
|
+
expect(plan.currentConfig?.propA).to.be.lessThan(plan.currentConfig?.propD as any);
|
|
461
|
+
expect(plan.currentConfig?.propD).to.be.lessThan(plan.currentConfig?.propE as any);
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
it('Applies stateful parameters in the order specified', async () => {
|
|
465
|
+
let timestampA;
|
|
466
|
+
const statefulParameterA = spy(new class extends TestStatefulParameter {
|
|
467
|
+
add = async (): Promise<void> => {
|
|
468
|
+
timestampA = performance.now();
|
|
469
|
+
}
|
|
470
|
+
modify = async (): Promise<void> => {
|
|
471
|
+
timestampA = performance.now();
|
|
472
|
+
}
|
|
473
|
+
remove = async (): Promise<void> => {
|
|
474
|
+
timestampA = performance.now();
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
let timestampB
|
|
479
|
+
const statefulParameterB = spy(new class extends TestStatefulParameter {
|
|
480
|
+
add = async (): Promise<void> => {
|
|
481
|
+
timestampB = performance.now();
|
|
482
|
+
}
|
|
483
|
+
modify = async (): Promise<void> => {
|
|
484
|
+
timestampB = performance.now();
|
|
485
|
+
}
|
|
486
|
+
remove = async (): Promise<void> => {
|
|
487
|
+
timestampB = performance.now();
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
let timestampC
|
|
492
|
+
const statefulParameterC = spy(new class extends TestStatefulParameter {
|
|
493
|
+
add = async (): Promise<void> => {
|
|
494
|
+
timestampC = performance.now();
|
|
495
|
+
}
|
|
496
|
+
modify = async (): Promise<void> => {
|
|
497
|
+
timestampC = performance.now();
|
|
498
|
+
}
|
|
499
|
+
remove = async (): Promise<void> => {
|
|
500
|
+
timestampC = performance.now();
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
const resource = spy(new class extends TestResource {
|
|
505
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
506
|
+
return {
|
|
507
|
+
id: 'resourceType',
|
|
508
|
+
operatingSystems: [OS.Darwin],
|
|
509
|
+
parameterSettings: {
|
|
510
|
+
propA: { type: 'stateful', definition: statefulParameterA, order: 3 },
|
|
511
|
+
propB: { type: 'stateful', definition: statefulParameterB, order: 1 },
|
|
512
|
+
propC: { type: 'stateful', definition: statefulParameterC, order: 2 },
|
|
513
|
+
},
|
|
514
|
+
removeStatefulParametersBeforeDestroy: true,
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
const controller = new ResourceController(resource);
|
|
520
|
+
await controller.apply(
|
|
521
|
+
Plan.fromResponse({
|
|
522
|
+
resourceType: 'resourceType',
|
|
523
|
+
operation: ResourceOperation.CREATE,
|
|
524
|
+
parameters: [
|
|
525
|
+
{ name: 'propA', operation: ParameterOperation.ADD, previousValue: null, newValue: null },
|
|
526
|
+
{ name: 'propB', operation: ParameterOperation.ADD, previousValue: null, newValue: null },
|
|
527
|
+
{ name: 'propC', operation: ParameterOperation.ADD, previousValue: null, newValue: null },
|
|
528
|
+
],
|
|
529
|
+
isStateful: false,
|
|
530
|
+
}, {}) as any
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
if (!timestampB || !timestampC || !timestampA) {
|
|
534
|
+
throw new Error('Variable not initialized')
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
expect(timestampB).to.be.lessThan(timestampC as any);
|
|
538
|
+
expect(timestampC).to.be.lessThan(timestampA as any);
|
|
539
|
+
timestampA = 0;
|
|
540
|
+
timestampB = 0;
|
|
541
|
+
timestampC = 0;
|
|
542
|
+
|
|
543
|
+
await controller.apply(
|
|
544
|
+
Plan.fromResponse({
|
|
545
|
+
resourceType: 'resourceType',
|
|
546
|
+
operation: ResourceOperation.MODIFY,
|
|
547
|
+
parameters: [
|
|
548
|
+
{ name: 'propA', operation: ParameterOperation.MODIFY, previousValue: null, newValue: null },
|
|
549
|
+
{ name: 'propB', operation: ParameterOperation.MODIFY, previousValue: null, newValue: null },
|
|
550
|
+
{ name: 'propC', operation: ParameterOperation.MODIFY, previousValue: null, newValue: null },
|
|
551
|
+
],
|
|
552
|
+
isStateful: false,
|
|
553
|
+
}, {}) as any
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
expect(timestampB).to.be.lessThan(timestampC as any);
|
|
557
|
+
expect(timestampC).to.be.lessThan(timestampA as any);
|
|
558
|
+
timestampA = 0;
|
|
559
|
+
timestampB = 0;
|
|
560
|
+
timestampC = 0;
|
|
561
|
+
|
|
562
|
+
await controller.apply(
|
|
563
|
+
Plan.fromResponse({
|
|
564
|
+
resourceType: 'resourceType',
|
|
565
|
+
operation: ResourceOperation.DESTROY,
|
|
566
|
+
parameters: [
|
|
567
|
+
{ name: 'propA', operation: ParameterOperation.REMOVE, previousValue: null, newValue: null },
|
|
568
|
+
{ name: 'propB', operation: ParameterOperation.REMOVE, previousValue: null, newValue: null },
|
|
569
|
+
{ name: 'propC', operation: ParameterOperation.REMOVE, previousValue: null, newValue: null },
|
|
570
|
+
],
|
|
571
|
+
isStateful: false,
|
|
572
|
+
}, {}) as any
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
expect(timestampB).to.be.lessThan(timestampC as any);
|
|
576
|
+
expect(timestampC).to.be.lessThan(timestampA as any);
|
|
577
|
+
})
|
|
578
|
+
|
|
579
|
+
it('Supports transform parameters', async () => {
|
|
580
|
+
const resource = spy(new class extends TestResource {
|
|
581
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
582
|
+
return {
|
|
583
|
+
id: 'resourceType',
|
|
584
|
+
operatingSystems: [OS.Darwin],
|
|
585
|
+
transformation: {
|
|
586
|
+
to: (desired) => ({
|
|
587
|
+
propA: 'propA',
|
|
588
|
+
propB: 10,
|
|
589
|
+
}),
|
|
590
|
+
from: (current) => ({
|
|
591
|
+
propA: 'propA',
|
|
592
|
+
propB: 10,
|
|
593
|
+
})
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
599
|
+
return {
|
|
600
|
+
propA: 'propA',
|
|
601
|
+
propB: 10,
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
const controller = new ResourceController(resource);
|
|
607
|
+
const plan = await controller.plan({ type: 'resourceType' }, { propC: 'abc' } as any, null, false);
|
|
608
|
+
|
|
609
|
+
expect(resource.refresh.called).to.be.true;
|
|
610
|
+
expect(resource.refresh.getCall(0).firstArg['propA']).to.exist;
|
|
611
|
+
expect(resource.refresh.getCall(0).firstArg['propB']).to.exist;
|
|
612
|
+
expect(resource.refresh.getCall(0).firstArg['propC']).to.not.exist;
|
|
613
|
+
|
|
614
|
+
expect(plan.desiredConfig?.propA).to.eq('propA');
|
|
615
|
+
expect(plan.desiredConfig?.propB).to.eq(10);
|
|
616
|
+
expect(plan.desiredConfig?.propC).to.be.undefined;
|
|
617
|
+
|
|
618
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
it('Supports transform parameters for state parameters', async () => {
|
|
622
|
+
const resource = spy(new class extends TestResource {
|
|
623
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
624
|
+
return {
|
|
625
|
+
id: 'resourceType',
|
|
626
|
+
operatingSystems: [OS.Darwin],
|
|
627
|
+
transformation: {
|
|
628
|
+
to: (desired) => ({
|
|
629
|
+
propA: 'propA',
|
|
630
|
+
propB: 10,
|
|
631
|
+
}),
|
|
632
|
+
from: (desired) => ({
|
|
633
|
+
propA: 'propA',
|
|
634
|
+
propB: 10,
|
|
635
|
+
})
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
641
|
+
return {
|
|
642
|
+
propA: 'propA',
|
|
643
|
+
propB: 10,
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
const controller = new ResourceController(resource);
|
|
649
|
+
const plan = await controller.plan({ type: 'resourceType' }, null, { propC: 'abc' }, true);
|
|
650
|
+
|
|
651
|
+
expect(resource.refresh.called).to.be.true;
|
|
652
|
+
expect(resource.refresh.getCall(0).firstArg['propA']).to.exist;
|
|
653
|
+
expect(resource.refresh.getCall(0).firstArg['propB']).to.exist;
|
|
654
|
+
expect(resource.refresh.getCall(0).firstArg['propC']).to.not.exist;
|
|
655
|
+
|
|
656
|
+
expect(plan.currentConfig?.propA).to.eq('propA');
|
|
657
|
+
expect(plan.currentConfig?.propB).to.eq(10);
|
|
658
|
+
expect(plan.currentConfig?.propC).to.be.undefined;
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
it('Allows import required parameters customization', () => {
|
|
662
|
+
const resource = new class extends TestResource {
|
|
663
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
664
|
+
return {
|
|
665
|
+
id: 'resourceType',
|
|
666
|
+
operatingSystems: [OS.Darwin],
|
|
667
|
+
importAndDestroy: {
|
|
668
|
+
requiredParameters: [
|
|
669
|
+
'propA',
|
|
670
|
+
'propB',
|
|
671
|
+
]
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
})
|
|
677
|
+
|
|
678
|
+
it('Applies default input transformations', async () => {
|
|
679
|
+
const home = os.homedir()
|
|
680
|
+
const testPath = path.join(home, 'test/folder');
|
|
681
|
+
|
|
682
|
+
const resource = new class extends TestResource {
|
|
683
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
684
|
+
return {
|
|
685
|
+
id: 'resourceType',
|
|
686
|
+
operatingSystems: [OS.Darwin],
|
|
687
|
+
parameterSettings: {
|
|
688
|
+
propA: { type: 'directory' }
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
694
|
+
return { propA: testPath }
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
const controller = new ResourceController(resource);
|
|
699
|
+
const plan = await controller.plan({ type: 'resourceType' }, { propA: '~/test/folder' } as any, null, false);
|
|
700
|
+
|
|
701
|
+
expect(plan.changeSet.parameterChanges[0]).toMatchObject({
|
|
702
|
+
operation: ParameterOperation.NOOP,
|
|
703
|
+
newValue: testPath,
|
|
704
|
+
previousValue: testPath,
|
|
705
|
+
})
|
|
706
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
707
|
+
})
|
|
708
|
+
|
|
709
|
+
it('Ignores setting parameters when planning', async () => {
|
|
710
|
+
const resource = new class extends TestResource {
|
|
711
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
712
|
+
return {
|
|
713
|
+
id: 'resourceType',
|
|
714
|
+
operatingSystems: [OS.Darwin],
|
|
715
|
+
parameterSettings: {
|
|
716
|
+
propA: { type: 'string', setting: true },
|
|
717
|
+
propB: { type: 'number' }
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
723
|
+
return { propB: 64 }
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
const controller = new ResourceController(resource);
|
|
728
|
+
const plan = await controller.plan({ type: 'resourceType' }, { propA: 'setting', propB: 64 } as any, null, false);
|
|
729
|
+
|
|
730
|
+
expect(plan.changeSet.parameterChanges).toMatchObject(
|
|
731
|
+
expect.arrayContaining([
|
|
732
|
+
{
|
|
733
|
+
name: 'propA',
|
|
734
|
+
operation: ParameterOperation.NOOP,
|
|
735
|
+
previousValue: null,
|
|
736
|
+
newValue: 'setting',
|
|
737
|
+
isSensitive: false,
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
name: 'propB',
|
|
741
|
+
operation: ParameterOperation.NOOP,
|
|
742
|
+
previousValue: 64,
|
|
743
|
+
newValue: 64,
|
|
744
|
+
isSensitive: false,
|
|
745
|
+
}
|
|
746
|
+
])
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
it('Accepts an input parameters for imports', () => {
|
|
753
|
+
const resource = new class extends TestResource {
|
|
754
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
755
|
+
return {
|
|
756
|
+
id: 'resourceType',
|
|
757
|
+
operatingSystems: [OS.Darwin],
|
|
758
|
+
importAndDestroy: {
|
|
759
|
+
requiredParameters: ['propA'],
|
|
760
|
+
refreshKeys: ['propB', 'propA'],
|
|
761
|
+
defaultRefreshValues: {
|
|
762
|
+
propB: 6,
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
it('Accepts a string isEqual method which selects from one of the defaults', async () => {
|
|
771
|
+
const resource = new class extends TestResource {
|
|
772
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
773
|
+
return {
|
|
774
|
+
id: 'resourceType',
|
|
775
|
+
operatingSystems: [OS.Darwin],
|
|
776
|
+
parameterSettings: {
|
|
777
|
+
propA: { type: 'string', isEqual: 'version' }
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
783
|
+
return {
|
|
784
|
+
propA: '10.0.0'
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
const controller = new ResourceController(resource);
|
|
790
|
+
|
|
791
|
+
const result = await controller.plan({ type: 'resourceType' }, { propA: '10.0' }, null, false);
|
|
792
|
+
expect(result.changeSet).toMatchObject({
|
|
793
|
+
operation: ResourceOperation.NOOP,
|
|
794
|
+
})
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
it('Object equals method (works when equal)', async () => {
|
|
798
|
+
const resource = new class extends TestResource {
|
|
799
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
800
|
+
return {
|
|
801
|
+
id: 'resourceType',
|
|
802
|
+
operatingSystems: [OS.Darwin],
|
|
803
|
+
parameterSettings: {
|
|
804
|
+
propD: { type: 'object' }
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
810
|
+
return {
|
|
811
|
+
propD: {
|
|
812
|
+
testA: 'a',
|
|
813
|
+
testB: 'b',
|
|
814
|
+
testC: 10,
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
const controller = new ResourceController(resource);
|
|
821
|
+
|
|
822
|
+
const result = await controller.plan(
|
|
823
|
+
{ type: 'resourceType' },
|
|
824
|
+
{
|
|
825
|
+
propD: {
|
|
826
|
+
testC: 10,
|
|
827
|
+
testA: 'a',
|
|
828
|
+
testB: 'b',
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
null,
|
|
832
|
+
false
|
|
833
|
+
);
|
|
834
|
+
|
|
835
|
+
expect(result.changeSet).toMatchObject({
|
|
836
|
+
operation: ResourceOperation.NOOP,
|
|
837
|
+
})
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
it('Object equals method (works when not equal)', async () => {
|
|
841
|
+
const resource = new class extends TestResource {
|
|
842
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
843
|
+
return {
|
|
844
|
+
id: 'resourceType',
|
|
845
|
+
operatingSystems: [OS.Darwin],
|
|
846
|
+
parameterSettings: {
|
|
847
|
+
propD: { type: 'object' }
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
853
|
+
return {
|
|
854
|
+
propD: {
|
|
855
|
+
testA: 'a',
|
|
856
|
+
testB: 'b',
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
const controller = new ResourceController(resource);
|
|
863
|
+
|
|
864
|
+
const result = await controller.plan(
|
|
865
|
+
{ type: 'resourceType' },
|
|
866
|
+
{
|
|
867
|
+
propD: {
|
|
868
|
+
testC: 10,
|
|
869
|
+
testA: 'a',
|
|
870
|
+
testB: 'b',
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
null,
|
|
874
|
+
false
|
|
875
|
+
);
|
|
876
|
+
|
|
877
|
+
expect(result.changeSet).toMatchObject({
|
|
878
|
+
operation: ResourceOperation.RECREATE,
|
|
879
|
+
})
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
it('Transforms input parameters', async () => {
|
|
883
|
+
const resource = new class extends TestResource {
|
|
884
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
885
|
+
return {
|
|
886
|
+
id: 'resourceType',
|
|
887
|
+
operatingSystems: [OS.Darwin],
|
|
888
|
+
parameterSettings: {
|
|
889
|
+
propD: {
|
|
890
|
+
type: 'array',
|
|
891
|
+
transformation: {
|
|
892
|
+
to: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
|
|
893
|
+
Object.entries(h)
|
|
894
|
+
.map(([k, v]) => [
|
|
895
|
+
k,
|
|
896
|
+
typeof v === 'boolean'
|
|
897
|
+
? (v ? 'yes' : 'no') // The file takes 'yes' or 'no' instead of booleans
|
|
898
|
+
: v,
|
|
899
|
+
])
|
|
900
|
+
)
|
|
901
|
+
),
|
|
902
|
+
from: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
|
|
903
|
+
Object.entries(h)
|
|
904
|
+
.map(([k, v]) => [
|
|
905
|
+
k,
|
|
906
|
+
v === 'yes',
|
|
907
|
+
])
|
|
908
|
+
))
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
916
|
+
expect(parameters.propD[0].AddKeysToAgent).to.eq('yes')
|
|
917
|
+
expect(parameters.propD[1].AddKeysToAgent).to.eq('yes')
|
|
918
|
+
expect(parameters.propD[1].UseKeychain).to.eq('yes')
|
|
919
|
+
expect(parameters.propD[2].PasswordAuthentication).to.eq('yes')
|
|
920
|
+
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const controller = new ResourceController(resource);
|
|
926
|
+
await controller.plan(
|
|
927
|
+
{ type: 'resourceType' },
|
|
928
|
+
{
|
|
929
|
+
propD: [
|
|
930
|
+
{
|
|
931
|
+
Host: 'new.com',
|
|
932
|
+
AddKeysToAgent: true,
|
|
933
|
+
IdentityFile: 'id_ed25519'
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
Host: 'github.com',
|
|
937
|
+
AddKeysToAgent: true,
|
|
938
|
+
UseKeychain: true,
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
Match: 'User bob,joe,phil',
|
|
942
|
+
PasswordAuthentication: true,
|
|
943
|
+
}
|
|
944
|
+
]
|
|
945
|
+
},
|
|
946
|
+
null,
|
|
947
|
+
false
|
|
948
|
+
);
|
|
949
|
+
|
|
950
|
+
})
|
|
951
|
+
|
|
952
|
+
it('Transforms input parameters for stateful parameters', async () => {
|
|
953
|
+
const sp = new class extends TestStatefulParameter {
|
|
954
|
+
getSettings(): any {
|
|
955
|
+
return {
|
|
956
|
+
type: 'array',
|
|
957
|
+
transformation: {
|
|
958
|
+
to: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
|
|
959
|
+
Object.entries(h)
|
|
960
|
+
.map(([k, v]) => [
|
|
961
|
+
k,
|
|
962
|
+
typeof v === 'boolean'
|
|
963
|
+
? (v ? 'yes' : 'no') // The file takes 'yes' or 'no' instead of booleans
|
|
964
|
+
: v,
|
|
965
|
+
])
|
|
966
|
+
)
|
|
967
|
+
),
|
|
968
|
+
from: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
|
|
969
|
+
Object.entries(h)
|
|
970
|
+
.map(([k, v]) => [
|
|
971
|
+
k,
|
|
972
|
+
v === 'yes',
|
|
973
|
+
])
|
|
974
|
+
))
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
async refresh(desired: any): Promise<any | null> {
|
|
980
|
+
expect(desired[0].AddKeysToAgent).to.eq('yes')
|
|
981
|
+
expect(desired[1].AddKeysToAgent).to.eq('yes')
|
|
982
|
+
expect(desired[1].UseKeychain).to.eq('yes')
|
|
983
|
+
expect(desired[2].PasswordAuthentication).to.eq('yes')
|
|
984
|
+
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const resource = new class extends TestResource {
|
|
990
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
991
|
+
return {
|
|
992
|
+
id: 'resourceType',
|
|
993
|
+
operatingSystems: [OS.Darwin],
|
|
994
|
+
parameterSettings: {
|
|
995
|
+
propD: { type: 'stateful', definition: sp }
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const controller = new ResourceController(resource);
|
|
1002
|
+
await controller.plan(
|
|
1003
|
+
{ type: 'resourceType' },
|
|
1004
|
+
{
|
|
1005
|
+
propD: [
|
|
1006
|
+
{
|
|
1007
|
+
Host: 'new.com',
|
|
1008
|
+
AddKeysToAgent: true,
|
|
1009
|
+
IdentityFile: 'id_ed25519'
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
Host: 'github.com',
|
|
1013
|
+
AddKeysToAgent: true,
|
|
1014
|
+
UseKeychain: true,
|
|
1015
|
+
},
|
|
1016
|
+
{
|
|
1017
|
+
Match: 'User bob,joe,phil',
|
|
1018
|
+
PasswordAuthentication: true,
|
|
1019
|
+
}
|
|
1020
|
+
]
|
|
1021
|
+
},
|
|
1022
|
+
null,
|
|
1023
|
+
false
|
|
1024
|
+
);
|
|
1025
|
+
|
|
1026
|
+
})
|
|
1027
|
+
|
|
1028
|
+
it('Supports equality check for itemType', async () => {
|
|
1029
|
+
const resource = new class extends TestResource {
|
|
1030
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
1031
|
+
return {
|
|
1032
|
+
id: 'resourceType',
|
|
1033
|
+
operatingSystems: [OS.Darwin],
|
|
1034
|
+
parameterSettings: {
|
|
1035
|
+
propA: { type: 'array', itemType: 'version' }
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
1041
|
+
return {
|
|
1042
|
+
propA: ['10.0.0']
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
const controller = new ResourceController(resource);
|
|
1048
|
+
|
|
1049
|
+
const result = await controller.plan({ type: 'resourceType' }, { propA: ['10.0'] }, null, false);
|
|
1050
|
+
expect(result.changeSet).toMatchObject({
|
|
1051
|
+
operation: ResourceOperation.NOOP,
|
|
1052
|
+
})
|
|
1053
|
+
})
|
|
1054
|
+
|
|
1055
|
+
it('Supports transformations for itemType', async () => {
|
|
1056
|
+
const home = os.homedir()
|
|
1057
|
+
const testPath = path.join(home, 'test/folder');
|
|
1058
|
+
|
|
1059
|
+
const resource = new class extends TestResource {
|
|
1060
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
1061
|
+
return {
|
|
1062
|
+
id: 'resourceType',
|
|
1063
|
+
operatingSystems: [OS.Darwin],
|
|
1064
|
+
parameterSettings: {
|
|
1065
|
+
propA: { type: 'array', itemType: 'directory' }
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
1071
|
+
return {
|
|
1072
|
+
propA: [testPath]
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
const controller = new ResourceController(resource);
|
|
1078
|
+
|
|
1079
|
+
const result = await controller.plan({ type: 'resourceType' }, { propA: ['~/test/folder'] }, null, false);
|
|
1080
|
+
expect(result.changeSet).toMatchObject({
|
|
1081
|
+
operation: ResourceOperation.NOOP,
|
|
1082
|
+
})
|
|
1083
|
+
})
|
|
1084
|
+
|
|
1085
|
+
it('Supports matching using the identfying parameters', async () => {
|
|
1086
|
+
const home = os.homedir()
|
|
1087
|
+
const testPath = path.join(home, 'test/folder');
|
|
1088
|
+
|
|
1089
|
+
const resource = new class extends TestResource {
|
|
1090
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
1091
|
+
return {
|
|
1092
|
+
id: 'resourceType',
|
|
1093
|
+
operatingSystems: [OS.Darwin],
|
|
1094
|
+
parameterSettings: {
|
|
1095
|
+
propA: { type: 'array', itemType: 'directory' }
|
|
1096
|
+
},
|
|
1097
|
+
allowMultiple: {
|
|
1098
|
+
identifyingParameters: ['propA']
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
const controller = new ResourceController(resource);
|
|
1105
|
+
expect(controller.parsedSettings.matcher({
|
|
1106
|
+
propA: [testPath],
|
|
1107
|
+
propB: 'random1',
|
|
1108
|
+
}, {
|
|
1109
|
+
propA: [testPath],
|
|
1110
|
+
propB: 'random2',
|
|
1111
|
+
})).to.be.true;
|
|
1112
|
+
|
|
1113
|
+
expect(controller.parsedSettings.matcher({
|
|
1114
|
+
propA: [testPath],
|
|
1115
|
+
propB: 'random1',
|
|
1116
|
+
}, {
|
|
1117
|
+
propA: [testPath, testPath],
|
|
1118
|
+
propB: 'random2',
|
|
1119
|
+
})).to.be.false;
|
|
1120
|
+
})
|
|
1121
|
+
|
|
1122
|
+
it('Supports matching using custom matcher', async () => {
|
|
1123
|
+
const home = os.homedir()
|
|
1124
|
+
const testPath = path.join(home, 'test/folder');
|
|
1125
|
+
|
|
1126
|
+
const resource = new class extends TestResource {
|
|
1127
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
1128
|
+
return {
|
|
1129
|
+
id: 'resourceType',
|
|
1130
|
+
operatingSystems: [OS.Darwin],
|
|
1131
|
+
parameterSettings: {
|
|
1132
|
+
propA: { type: 'array', itemType: 'directory' }
|
|
1133
|
+
},
|
|
1134
|
+
allowMultiple: {
|
|
1135
|
+
identifyingParameters: ['propA'],
|
|
1136
|
+
matcher: () => false,
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
const controller = new ResourceController(resource);
|
|
1143
|
+
expect(controller.parsedSettings.matcher({
|
|
1144
|
+
propA: [testPath],
|
|
1145
|
+
propB: 'random1',
|
|
1146
|
+
}, {
|
|
1147
|
+
propA: [testPath],
|
|
1148
|
+
propB: 'random2',
|
|
1149
|
+
})).to.be.false;
|
|
1150
|
+
})
|
|
1151
|
+
|
|
1152
|
+
it('Can match directories 1', async () => {
|
|
1153
|
+
const resource = new class extends TestResource {
|
|
1154
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
1155
|
+
return {
|
|
1156
|
+
id: 'resourceType',
|
|
1157
|
+
operatingSystems: [OS.Darwin],
|
|
1158
|
+
parameterSettings: {
|
|
1159
|
+
propA: { type: 'directory' }
|
|
1160
|
+
},
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
|
|
1165
|
+
const controller = new ResourceController(resource);
|
|
1166
|
+
const transformations = controller.parsedSettings.inputTransformations.propA;
|
|
1167
|
+
|
|
1168
|
+
const to = transformations!.to('$HOME/abc/def')
|
|
1169
|
+
expect(to).to.eq(os.homedir() + '/abc/def')
|
|
1170
|
+
|
|
1171
|
+
const from = transformations!.from(os.homedir() + '/abc/def')
|
|
1172
|
+
expect(from).to.eq('~/abc/def')
|
|
1173
|
+
|
|
1174
|
+
const from2 = transformations!.from(os.homedir() + '/abc/def', '$HOME/abc/def')
|
|
1175
|
+
expect(from2).to.eq('$HOME/abc/def')
|
|
1176
|
+
|
|
1177
|
+
})
|
|
1178
|
+
|
|
1179
|
+
it('Can match directories 2', async () => {
|
|
1180
|
+
|
|
1181
|
+
const schema = z.object({
|
|
1182
|
+
propA: z.string(),
|
|
1183
|
+
propB: z.number(),
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
const resource = new class extends TestResource {
|
|
1187
|
+
getSettings(): ResourceSettings<z.infer<typeof schema>> {
|
|
1188
|
+
return {
|
|
1189
|
+
id: 'resourceType',
|
|
1190
|
+
schema,
|
|
1191
|
+
operatingSystems: [OS.Darwin],
|
|
1192
|
+
parameterSettings: {
|
|
1193
|
+
propA: { type: 'directory' }
|
|
1194
|
+
},
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
const controller = new ResourceController(resource);
|
|
1200
|
+
const transformations = controller.parsedSettings.inputTransformations.propA;
|
|
1201
|
+
|
|
1202
|
+
const to = transformations!.to('$HOME/abc/def')
|
|
1203
|
+
expect(to).to.eq(os.homedir() + '/abc/def')
|
|
1204
|
+
|
|
1205
|
+
const from = transformations!.from(os.homedir() + '/abc/def')
|
|
1206
|
+
expect(from).to.eq('~/abc/def')
|
|
1207
|
+
|
|
1208
|
+
const from2 = transformations!.from(os.homedir() + '/abc/def', '$HOME/abc/def')
|
|
1209
|
+
expect(from2).to.eq('$HOME/abc/def')
|
|
1210
|
+
|
|
1211
|
+
})
|
|
1212
|
+
|
|
1213
|
+
})
|