codify-plugin-lib 1.0.106 → 1.0.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/messages/handlers.d.ts +3 -1
- package/dist/messages/handlers.js +18 -7
- package/dist/resource/parsed-resource-settings.d.ts +2 -2
- package/dist/resource/parsed-resource-settings.js +1 -1
- package/dist/resource/resource-controller.js +1 -1
- package/dist/resource/resource-settings.d.ts +1 -1
- package/dist/resource/resource-settings.js +6 -0
- package/dist/utils/utils.js +3 -0
- package/package.json +2 -2
- package/src/messages/handlers.test.ts +65 -1
- package/src/messages/handlers.ts +21 -6
- package/src/resource/parsed-resource-settings.ts +2 -2
- package/src/resource/resource-controller.test.ts +22 -7
- package/src/resource/resource-controller.ts +1 -1
- package/src/resource/resource-settings.test.ts +118 -0
- package/src/resource/resource-settings.ts +8 -2
- package/src/utils/utils.ts +4 -0
|
@@ -2,11 +2,13 @@ import { Plugin } from '../plugin/plugin.js';
|
|
|
2
2
|
export declare class MessageHandler {
|
|
3
3
|
private ajv;
|
|
4
4
|
private readonly plugin;
|
|
5
|
-
private
|
|
5
|
+
private messageSchemaValidatorV1;
|
|
6
|
+
private messageSchemaValidatorV2;
|
|
6
7
|
private requestValidators;
|
|
7
8
|
private responseValidators;
|
|
8
9
|
constructor(plugin: Plugin);
|
|
9
10
|
onMessage(message: unknown): Promise<void>;
|
|
10
11
|
private validateMessage;
|
|
12
|
+
private validateMessageV2;
|
|
11
13
|
private handleErrors;
|
|
12
14
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Ajv } from 'ajv';
|
|
2
2
|
import addFormats from 'ajv-formats';
|
|
3
|
-
import { ApplyRequestDataSchema, ApplyResponseDataSchema, GetResourceInfoRequestDataSchema, GetResourceInfoResponseDataSchema, ImportRequestDataSchema, ImportResponseDataSchema, InitializeRequestDataSchema, InitializeResponseDataSchema, IpcMessageSchema, MessageStatus, PlanRequestDataSchema, PlanResponseDataSchema, ResourceSchema, ValidateRequestDataSchema, ValidateResponseDataSchema } from 'codify-schemas';
|
|
3
|
+
import { ApplyRequestDataSchema, ApplyResponseDataSchema, GetResourceInfoRequestDataSchema, GetResourceInfoResponseDataSchema, ImportRequestDataSchema, ImportResponseDataSchema, InitializeRequestDataSchema, InitializeResponseDataSchema, IpcMessageSchema, IpcMessageV2Schema, MessageStatus, PlanRequestDataSchema, PlanResponseDataSchema, ResourceSchema, ValidateRequestDataSchema, ValidateResponseDataSchema } from 'codify-schemas';
|
|
4
4
|
import { SudoError } from '../errors.js';
|
|
5
5
|
const SupportedRequests = {
|
|
6
6
|
'initialize': {
|
|
@@ -40,7 +40,8 @@ const SupportedRequests = {
|
|
|
40
40
|
export class MessageHandler {
|
|
41
41
|
ajv;
|
|
42
42
|
plugin;
|
|
43
|
-
|
|
43
|
+
messageSchemaValidatorV1;
|
|
44
|
+
messageSchemaValidatorV2;
|
|
44
45
|
requestValidators;
|
|
45
46
|
responseValidators;
|
|
46
47
|
constructor(plugin) {
|
|
@@ -48,7 +49,8 @@ export class MessageHandler {
|
|
|
48
49
|
addFormats.default(this.ajv);
|
|
49
50
|
this.ajv.addSchema(ResourceSchema);
|
|
50
51
|
this.plugin = plugin;
|
|
51
|
-
this.
|
|
52
|
+
this.messageSchemaValidatorV1 = this.ajv.compile(IpcMessageSchema);
|
|
53
|
+
this.messageSchemaValidatorV2 = this.ajv.compile(IpcMessageV2Schema);
|
|
52
54
|
this.requestValidators = new Map(Object.entries(SupportedRequests)
|
|
53
55
|
.map(([k, v]) => [k, this.ajv.compile(v.requestValidator)]));
|
|
54
56
|
this.responseValidators = new Map(Object.entries(SupportedRequests)
|
|
@@ -56,8 +58,8 @@ export class MessageHandler {
|
|
|
56
58
|
}
|
|
57
59
|
async onMessage(message) {
|
|
58
60
|
try {
|
|
59
|
-
if (!this.validateMessage(message)) {
|
|
60
|
-
throw new Error(`Plugin: ${this.plugin}. Message is malformed: ${JSON.stringify(this.
|
|
61
|
+
if (!this.validateMessageV2(message) && !this.validateMessage(message)) {
|
|
62
|
+
throw new Error(`Plugin: ${this.plugin}. Message is malformed: ${JSON.stringify(this.messageSchemaValidatorV1.errors, null, 2)}`);
|
|
61
63
|
}
|
|
62
64
|
if (!this.requestValidators.has(message.cmd)) {
|
|
63
65
|
throw new Error(`Plugin: ${this.plugin}. Unsupported message: ${message.cmd}`);
|
|
@@ -74,6 +76,8 @@ export class MessageHandler {
|
|
|
74
76
|
process.send({
|
|
75
77
|
cmd: message.cmd + '_Response',
|
|
76
78
|
data: result,
|
|
79
|
+
// @ts-expect-error TS2239
|
|
80
|
+
requestId: message.requestId || undefined,
|
|
77
81
|
status: MessageStatus.SUCCESS,
|
|
78
82
|
});
|
|
79
83
|
}
|
|
@@ -82,7 +86,10 @@ export class MessageHandler {
|
|
|
82
86
|
}
|
|
83
87
|
}
|
|
84
88
|
validateMessage(message) {
|
|
85
|
-
return this.
|
|
89
|
+
return this.messageSchemaValidatorV1(message);
|
|
90
|
+
}
|
|
91
|
+
validateMessageV2(message) {
|
|
92
|
+
return this.messageSchemaValidatorV2(message);
|
|
86
93
|
}
|
|
87
94
|
handleErrors(message, e) {
|
|
88
95
|
if (!message) {
|
|
@@ -91,11 +98,13 @@ export class MessageHandler {
|
|
|
91
98
|
if (!message.hasOwnProperty('cmd')) {
|
|
92
99
|
return;
|
|
93
100
|
}
|
|
94
|
-
// @ts-
|
|
101
|
+
// @ts-expect-error TS2239
|
|
95
102
|
const cmd = message.cmd + '_Response';
|
|
96
103
|
if (e instanceof SudoError) {
|
|
97
104
|
return process.send?.({
|
|
98
105
|
cmd,
|
|
106
|
+
// @ts-expect-error TS2239
|
|
107
|
+
requestId: message.requestId || undefined,
|
|
99
108
|
data: `Plugin: '${this.plugin.name}'. Forbidden usage of sudo for command '${e.command}'. Please contact the plugin developer to fix this.`,
|
|
100
109
|
status: MessageStatus.ERROR,
|
|
101
110
|
});
|
|
@@ -103,6 +112,8 @@ export class MessageHandler {
|
|
|
103
112
|
const isDebug = process.env.DEBUG?.includes('*') ?? false;
|
|
104
113
|
process.send?.({
|
|
105
114
|
cmd,
|
|
115
|
+
// @ts-expect-error TS2239
|
|
116
|
+
requestId: message.requestId || undefined,
|
|
106
117
|
data: isDebug ? e.stack : e.message,
|
|
107
118
|
status: MessageStatus.ERROR,
|
|
108
119
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StringIndexedObject } from 'codify-schemas';
|
|
2
2
|
import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
|
|
3
|
-
import { ArrayParameterSetting, DefaultParameterSetting, ResourceSettings } from './resource-settings.js';
|
|
3
|
+
import { ArrayParameterSetting, DefaultParameterSetting, ParameterSetting, ResourceSettings } from './resource-settings.js';
|
|
4
4
|
export interface ParsedStatefulParameterSetting extends DefaultParameterSetting {
|
|
5
5
|
type: 'stateful';
|
|
6
6
|
controller: StatefulParameterController<any, unknown>;
|
|
@@ -30,7 +30,7 @@ export declare class ParsedResourceSettings<T extends StringIndexedObject> imple
|
|
|
30
30
|
get statefulParameters(): Map<keyof T, StatefulParameterController<T, T[keyof T]>>;
|
|
31
31
|
get parameterSettings(): Record<keyof T, ParsedParameterSetting>;
|
|
32
32
|
get defaultValues(): Partial<Record<keyof T, unknown>>;
|
|
33
|
-
get inputTransformations(): Partial<Record<keyof T, (a: unknown) => unknown>>;
|
|
33
|
+
get inputTransformations(): Partial<Record<keyof T, (a: unknown, parameter: ParameterSetting) => unknown>>;
|
|
34
34
|
get statefulParameterOrder(): Map<keyof T, number>;
|
|
35
35
|
private validateSettings;
|
|
36
36
|
private validateParameterEqualsFn;
|
|
@@ -83,7 +83,7 @@ export class ParsedResourceSettings {
|
|
|
83
83
|
return {};
|
|
84
84
|
}
|
|
85
85
|
return Object.fromEntries(Object.entries(this.settings.parameterSettings)
|
|
86
|
-
.filter(([, v]) => resolveParameterTransformFn(v) !== undefined)
|
|
86
|
+
.filter(([_, v]) => resolveParameterTransformFn(v) !== undefined)
|
|
87
87
|
.map(([k, v]) => [k, resolveParameterTransformFn(v)]));
|
|
88
88
|
});
|
|
89
89
|
}
|
|
@@ -230,7 +230,7 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
230
230
|
if (config[key] === undefined || !inputTransformation) {
|
|
231
231
|
continue;
|
|
232
232
|
}
|
|
233
|
-
config[key] = await inputTransformation(config[key]);
|
|
233
|
+
config[key] = await inputTransformation(config[key], this.settings.parameterSettings[key]);
|
|
234
234
|
}
|
|
235
235
|
if (this.settings.inputTransformation) {
|
|
236
236
|
const { parameters, coreParameters } = splitUserConfig(config);
|
|
@@ -216,4 +216,4 @@ export interface StatefulParameterSetting extends DefaultParameterSetting {
|
|
|
216
216
|
}
|
|
217
217
|
export declare function resolveEqualsFn(parameter: ParameterSetting): (desired: unknown, current: unknown) => boolean;
|
|
218
218
|
export declare function resolveFnFromEqualsFnOrString(fnOrString: ((a: unknown, b: unknown) => boolean) | ParameterSettingType | undefined): ((a: unknown, b: unknown) => boolean) | undefined;
|
|
219
|
-
export declare function resolveParameterTransformFn(parameter: ParameterSetting): ((input: any) => Promise<any> | any) | undefined;
|
|
219
|
+
export declare function resolveParameterTransformFn(parameter: ParameterSetting): ((input: any, parameter: ParameterSetting) => Promise<any> | any) | undefined;
|
|
@@ -35,6 +35,12 @@ export function resolveFnFromEqualsFnOrString(fnOrString) {
|
|
|
35
35
|
}
|
|
36
36
|
const ParameterTransformationDefaults = {
|
|
37
37
|
'directory': (a) => path.resolve(untildify(String(a))),
|
|
38
|
+
'stateful': (a, b) => {
|
|
39
|
+
const sp = b;
|
|
40
|
+
return (sp.definition?.getSettings()?.inputTransformation)
|
|
41
|
+
? (sp.definition.getSettings().inputTransformation(a))
|
|
42
|
+
: a;
|
|
43
|
+
},
|
|
38
44
|
'string': String,
|
|
39
45
|
};
|
|
40
46
|
export function resolveParameterTransformFn(parameter) {
|
package/dist/utils/utils.js
CHANGED
|
@@ -73,6 +73,9 @@ export function untildify(pathWithTilde) {
|
|
|
73
73
|
return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
|
|
74
74
|
}
|
|
75
75
|
export function areArraysEqual(isElementEqual, desired, current) {
|
|
76
|
+
if (!desired || !current) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
76
79
|
if (!Array.isArray(desired) || !Array.isArray(current)) {
|
|
77
80
|
throw new Error(`A non-array value:
|
|
78
81
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codify-plugin-lib",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.108",
|
|
4
4
|
"description": "Library plugin library",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"ajv": "^8.12.0",
|
|
16
16
|
"ajv-formats": "^2.1.1",
|
|
17
|
-
"codify-schemas": "1.0.
|
|
17
|
+
"codify-schemas": "1.0.53",
|
|
18
18
|
"@npmcli/promise-spawn": "^7.0.1",
|
|
19
19
|
"uuid": "^10.0.0",
|
|
20
20
|
"lodash.isequal": "^4.5.0"
|
|
@@ -207,7 +207,6 @@ describe('Message handler tests', () => {
|
|
|
207
207
|
it('handles errors for apply (destroy)', async () => {
|
|
208
208
|
const resource = new TestResource()
|
|
209
209
|
const plugin = testPlugin(resource);
|
|
210
|
-
|
|
211
210
|
const handler = new MessageHandler(plugin);
|
|
212
211
|
|
|
213
212
|
process.send = (message) => {
|
|
@@ -228,6 +227,71 @@ describe('Message handler tests', () => {
|
|
|
228
227
|
}
|
|
229
228
|
}
|
|
230
229
|
})).rejects.to.not.throw;
|
|
230
|
+
|
|
231
|
+
process.send = undefined;
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('Supports ipc message v2 (success)', async () => {
|
|
235
|
+
const resource = new TestResource()
|
|
236
|
+
const plugin = testPlugin(resource);
|
|
237
|
+
const handler = new MessageHandler(plugin);
|
|
238
|
+
|
|
239
|
+
process.send = (message) => {
|
|
240
|
+
console.log(message)
|
|
241
|
+
expect(message).toMatchObject({
|
|
242
|
+
cmd: 'plan_Response',
|
|
243
|
+
requestId: 'abcdef',
|
|
244
|
+
status: MessageStatus.SUCCESS,
|
|
245
|
+
})
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
await expect(handler.onMessage({
|
|
250
|
+
cmd: 'plan',
|
|
251
|
+
requestId: 'abcdef',
|
|
252
|
+
data: {
|
|
253
|
+
desired: {
|
|
254
|
+
type: 'type',
|
|
255
|
+
name: 'name',
|
|
256
|
+
prop1: 'A',
|
|
257
|
+
prop2: 'B',
|
|
258
|
+
},
|
|
259
|
+
isStateful: false,
|
|
260
|
+
}
|
|
261
|
+
})).resolves.to.eq(undefined);
|
|
262
|
+
|
|
263
|
+
process.send = undefined;
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('Supports ipc message v2 (error)', async () => {
|
|
267
|
+
const resource = new TestResource()
|
|
268
|
+
const plugin = testPlugin(resource);
|
|
269
|
+
const handler = new MessageHandler(plugin);
|
|
270
|
+
|
|
271
|
+
process.send = (message) => {
|
|
272
|
+
expect(message).toMatchObject({
|
|
273
|
+
cmd: 'apply_Response',
|
|
274
|
+
requestId: 'abcdef',
|
|
275
|
+
status: MessageStatus.ERROR,
|
|
276
|
+
})
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
await expect(handler.onMessage({
|
|
281
|
+
cmd: 'apply', // Supposed to be a plan so that's why it throws
|
|
282
|
+
requestId: 'abcdef',
|
|
283
|
+
data: {
|
|
284
|
+
desired: {
|
|
285
|
+
type: 'type',
|
|
286
|
+
name: 'name',
|
|
287
|
+
prop1: 'A',
|
|
288
|
+
prop2: 'B',
|
|
289
|
+
},
|
|
290
|
+
isStateful: false,
|
|
291
|
+
}
|
|
292
|
+
})).resolves.to.eq(undefined);
|
|
293
|
+
|
|
294
|
+
process.send = undefined;
|
|
231
295
|
})
|
|
232
296
|
});
|
|
233
297
|
|
package/src/messages/handlers.ts
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
InitializeResponseDataSchema,
|
|
12
12
|
IpcMessage,
|
|
13
13
|
IpcMessageSchema,
|
|
14
|
+
IpcMessageV2,
|
|
15
|
+
IpcMessageV2Schema,
|
|
14
16
|
MessageStatus,
|
|
15
17
|
PlanRequestDataSchema,
|
|
16
18
|
PlanResponseDataSchema,
|
|
@@ -61,7 +63,8 @@ const SupportedRequests: Record<string, { handler: (plugin: Plugin, data: any) =
|
|
|
61
63
|
export class MessageHandler {
|
|
62
64
|
private ajv: Ajv;
|
|
63
65
|
private readonly plugin: Plugin;
|
|
64
|
-
private
|
|
66
|
+
private messageSchemaValidatorV1: ValidateFunction;
|
|
67
|
+
private messageSchemaValidatorV2: ValidateFunction;
|
|
65
68
|
private requestValidators: Map<string, ValidateFunction>;
|
|
66
69
|
private responseValidators: Map<string, ValidateFunction>;
|
|
67
70
|
|
|
@@ -71,7 +74,9 @@ export class MessageHandler {
|
|
|
71
74
|
this.ajv.addSchema(ResourceSchema);
|
|
72
75
|
this.plugin = plugin;
|
|
73
76
|
|
|
74
|
-
this.
|
|
77
|
+
this.messageSchemaValidatorV1 = this.ajv.compile(IpcMessageSchema);
|
|
78
|
+
this.messageSchemaValidatorV2 = this.ajv.compile(IpcMessageV2Schema);
|
|
79
|
+
|
|
75
80
|
this.requestValidators = new Map(
|
|
76
81
|
Object.entries(SupportedRequests)
|
|
77
82
|
.map(([k, v]) => [k, this.ajv.compile(v.requestValidator)])
|
|
@@ -84,8 +89,8 @@ export class MessageHandler {
|
|
|
84
89
|
|
|
85
90
|
async onMessage(message: unknown): Promise<void> {
|
|
86
91
|
try {
|
|
87
|
-
if (!this.validateMessage(message)) {
|
|
88
|
-
throw new Error(`Plugin: ${this.plugin}. Message is malformed: ${JSON.stringify(this.
|
|
92
|
+
if (!this.validateMessageV2(message) && !this.validateMessage(message)) {
|
|
93
|
+
throw new Error(`Plugin: ${this.plugin}. Message is malformed: ${JSON.stringify(this.messageSchemaValidatorV1.errors, null, 2)}`);
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
if (!this.requestValidators.has(message.cmd)) {
|
|
@@ -107,6 +112,8 @@ export class MessageHandler {
|
|
|
107
112
|
process.send!({
|
|
108
113
|
cmd: message.cmd + '_Response',
|
|
109
114
|
data: result,
|
|
115
|
+
// @ts-expect-error TS2239
|
|
116
|
+
requestId: message.requestId || undefined,
|
|
110
117
|
status: MessageStatus.SUCCESS,
|
|
111
118
|
})
|
|
112
119
|
|
|
@@ -116,7 +123,11 @@ export class MessageHandler {
|
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
private validateMessage(message: unknown): message is IpcMessage {
|
|
119
|
-
return this.
|
|
126
|
+
return this.messageSchemaValidatorV1(message);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private validateMessageV2(message: unknown): message is IpcMessageV2 {
|
|
130
|
+
return this.messageSchemaValidatorV2(message);
|
|
120
131
|
}
|
|
121
132
|
|
|
122
133
|
private handleErrors(message: unknown, e: Error) {
|
|
@@ -128,12 +139,14 @@ export class MessageHandler {
|
|
|
128
139
|
return;
|
|
129
140
|
}
|
|
130
141
|
|
|
131
|
-
// @ts-
|
|
142
|
+
// @ts-expect-error TS2239
|
|
132
143
|
const cmd = message.cmd + '_Response';
|
|
133
144
|
|
|
134
145
|
if (e instanceof SudoError) {
|
|
135
146
|
return process.send?.({
|
|
136
147
|
cmd,
|
|
148
|
+
// @ts-expect-error TS2239
|
|
149
|
+
requestId: message.requestId || undefined,
|
|
137
150
|
data: `Plugin: '${this.plugin.name}'. Forbidden usage of sudo for command '${e.command}'. Please contact the plugin developer to fix this.`,
|
|
138
151
|
status: MessageStatus.ERROR,
|
|
139
152
|
})
|
|
@@ -143,6 +156,8 @@ export class MessageHandler {
|
|
|
143
156
|
|
|
144
157
|
process.send?.({
|
|
145
158
|
cmd,
|
|
159
|
+
// @ts-expect-error TS2239
|
|
160
|
+
requestId: message.requestId || undefined,
|
|
146
161
|
data: isDebug ? e.stack : e.message,
|
|
147
162
|
status: MessageStatus.ERROR,
|
|
148
163
|
})
|
|
@@ -133,7 +133,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
|
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
get inputTransformations(): Partial<Record<keyof T, (a: unknown) => unknown>> {
|
|
136
|
+
get inputTransformations(): Partial<Record<keyof T, (a: unknown, parameter: ParameterSetting) => unknown>> {
|
|
137
137
|
return this.getFromCacheOrCreate('inputTransformations', () => {
|
|
138
138
|
if (!this.settings.parameterSettings) {
|
|
139
139
|
return {};
|
|
@@ -141,7 +141,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
|
|
|
141
141
|
|
|
142
142
|
return Object.fromEntries(
|
|
143
143
|
Object.entries(this.settings.parameterSettings)
|
|
144
|
-
.filter(([, v]) => resolveParameterTransformFn(v!) !== undefined)
|
|
144
|
+
.filter(([_, v]) => resolveParameterTransformFn(v!) !== undefined)
|
|
145
145
|
.map(([k, v]) => [k, resolveParameterTransformFn(v!)] as const)
|
|
146
146
|
) as Record<keyof T, (a: unknown) => unknown>;
|
|
147
147
|
});
|
|
@@ -401,12 +401,19 @@ describe('Resource tests', () => {
|
|
|
401
401
|
type: 'version'
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
|
+
|
|
404
405
|
override async refresh(desired: any, config: Partial<any>): Promise<any> {
|
|
405
406
|
return null;
|
|
406
407
|
}
|
|
407
|
-
|
|
408
|
-
override async
|
|
409
|
-
|
|
408
|
+
|
|
409
|
+
override async add(valueToAdd: any, plan: Plan<any>): Promise<void> {
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
override async modify(newValue: any, previousValue: any, plan: Plan<any>): Promise<void> {
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
override async remove(valueToRemove: any, plan: Plan<any>): Promise<void> {
|
|
416
|
+
}
|
|
410
417
|
}
|
|
411
418
|
|
|
412
419
|
const parameter2 = new class extends ArrayStatefulParameter<any, any> {
|
|
@@ -420,8 +427,12 @@ describe('Resource tests', () => {
|
|
|
420
427
|
override async refresh(desired: any[] | null, config: Partial<any>): Promise<any[] | null> {
|
|
421
428
|
return null;
|
|
422
429
|
}
|
|
423
|
-
|
|
424
|
-
override async
|
|
430
|
+
|
|
431
|
+
override async addItem(item: any, plan: Plan<any>): Promise<void> {
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
override async removeItem(item: any, plan: Plan<any>): Promise<void> {
|
|
435
|
+
}
|
|
425
436
|
}
|
|
426
437
|
|
|
427
438
|
const p1Spy = spy(parameter1);
|
|
@@ -471,8 +482,12 @@ describe('Resource tests', () => {
|
|
|
471
482
|
async refresh(desired: any[] | null, config: Partial<any>): Promise<any[] | null> {
|
|
472
483
|
return ['20']
|
|
473
484
|
}
|
|
474
|
-
|
|
475
|
-
async
|
|
485
|
+
|
|
486
|
+
async addItem(item: any, plan: Plan<any>): Promise<void> {
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async removeItem(item: any, plan: Plan<any>): Promise<void> {
|
|
490
|
+
}
|
|
476
491
|
})
|
|
477
492
|
|
|
478
493
|
const resource = new class extends TestResource {
|
|
@@ -318,7 +318,7 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
318
318
|
continue;
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
(config as Record<string, unknown>)[key] = await inputTransformation(config[key]);
|
|
321
|
+
(config as Record<string, unknown>)[key] = await inputTransformation(config[key], this.settings.parameterSettings![key]!);
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
if (this.settings.inputTransformation) {
|
|
@@ -818,4 +818,122 @@ describe('Resource parameter tests', () => {
|
|
|
818
818
|
operation: ResourceOperation.RECREATE,
|
|
819
819
|
})
|
|
820
820
|
});
|
|
821
|
+
|
|
822
|
+
it('Transforms input parameters', async () => {
|
|
823
|
+
const resource = new class extends TestResource {
|
|
824
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
825
|
+
return {
|
|
826
|
+
id: 'resourceType',
|
|
827
|
+
parameterSettings: {
|
|
828
|
+
propD: {
|
|
829
|
+
type: 'array',
|
|
830
|
+
inputTransformation: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
|
|
831
|
+
Object.entries(h)
|
|
832
|
+
.map(([k, v]) => [
|
|
833
|
+
k,
|
|
834
|
+
typeof v === 'boolean'
|
|
835
|
+
? (v ? 'yes' : 'no') // The file takes 'yes' or 'no' instead of booleans
|
|
836
|
+
: v,
|
|
837
|
+
])
|
|
838
|
+
)
|
|
839
|
+
)
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
846
|
+
expect(parameters.propD[0].AddKeysToAgent).to.eq('yes')
|
|
847
|
+
expect(parameters.propD[1].AddKeysToAgent).to.eq('yes')
|
|
848
|
+
expect(parameters.propD[1].UseKeychain).to.eq('yes')
|
|
849
|
+
expect(parameters.propD[2].PasswordAuthentication).to.eq('yes')
|
|
850
|
+
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const controller = new ResourceController(resource);
|
|
856
|
+
await controller.plan({
|
|
857
|
+
type: 'resourceType',
|
|
858
|
+
propD: [
|
|
859
|
+
{
|
|
860
|
+
Host: 'new.com',
|
|
861
|
+
AddKeysToAgent: true,
|
|
862
|
+
IdentityFile: 'id_ed25519'
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
Host: 'github.com',
|
|
866
|
+
AddKeysToAgent: true,
|
|
867
|
+
UseKeychain: true,
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
Match: 'User bob,joe,phil',
|
|
871
|
+
PasswordAuthentication: true,
|
|
872
|
+
}
|
|
873
|
+
]
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
})
|
|
877
|
+
|
|
878
|
+
it('Transforms input parameters for stateful parameters', async () => {
|
|
879
|
+
const sp = new class extends TestStatefulParameter {
|
|
880
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
881
|
+
return {
|
|
882
|
+
type: 'array',
|
|
883
|
+
inputTransformation: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
|
|
884
|
+
Object.entries(h)
|
|
885
|
+
.map(([k, v]) => [
|
|
886
|
+
k,
|
|
887
|
+
typeof v === 'boolean'
|
|
888
|
+
? (v ? 'yes' : 'no') // The file takes 'yes' or 'no' instead of booleans
|
|
889
|
+
: v,
|
|
890
|
+
])
|
|
891
|
+
)
|
|
892
|
+
)
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
async refresh(desired: any): Promise<Partial<TestConfig> | null> {
|
|
897
|
+
expect(desired[0].AddKeysToAgent).to.eq('yes')
|
|
898
|
+
expect(desired[1].AddKeysToAgent).to.eq('yes')
|
|
899
|
+
expect(desired[1].UseKeychain).to.eq('yes')
|
|
900
|
+
expect(desired[2].PasswordAuthentication).to.eq('yes')
|
|
901
|
+
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
const resource = new class extends TestResource {
|
|
907
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
908
|
+
return {
|
|
909
|
+
id: 'resourceType',
|
|
910
|
+
parameterSettings: {
|
|
911
|
+
propD: { type: 'stateful', definition: sp }
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const controller = new ResourceController(resource);
|
|
918
|
+
await controller.plan({
|
|
919
|
+
type: 'resourceType',
|
|
920
|
+
propD: [
|
|
921
|
+
{
|
|
922
|
+
Host: 'new.com',
|
|
923
|
+
AddKeysToAgent: true,
|
|
924
|
+
IdentityFile: 'id_ed25519'
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
Host: 'github.com',
|
|
928
|
+
AddKeysToAgent: true,
|
|
929
|
+
UseKeychain: true,
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
Match: 'User bob,joe,phil',
|
|
933
|
+
PasswordAuthentication: true,
|
|
934
|
+
}
|
|
935
|
+
]
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
})
|
|
821
939
|
})
|
|
@@ -303,13 +303,19 @@ export function resolveFnFromEqualsFnOrString(
|
|
|
303
303
|
return fnOrString as ((a: unknown, b: unknown) => boolean) | undefined;
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
const ParameterTransformationDefaults: Partial<Record<ParameterSettingType, (input: any) => Promise<any> | any>> = {
|
|
306
|
+
const ParameterTransformationDefaults: Partial<Record<ParameterSettingType, (input: any, parameter: ParameterSetting) => Promise<any> | any>> = {
|
|
307
307
|
'directory': (a: unknown) => path.resolve(untildify(String(a))),
|
|
308
|
+
'stateful': (a: unknown, b: ParameterSetting) => {
|
|
309
|
+
const sp = b as StatefulParameterSetting;
|
|
310
|
+
return (sp.definition?.getSettings()?.inputTransformation)
|
|
311
|
+
? (sp.definition.getSettings().inputTransformation!(a))
|
|
312
|
+
: a;
|
|
313
|
+
},
|
|
308
314
|
'string': String,
|
|
309
315
|
}
|
|
310
316
|
|
|
311
317
|
export function resolveParameterTransformFn(
|
|
312
318
|
parameter: ParameterSetting
|
|
313
|
-
): ((input: any) => Promise<any> | any) | undefined {
|
|
319
|
+
): ((input: any, parameter: ParameterSetting) => Promise<any> | any) | undefined {
|
|
314
320
|
return parameter.inputTransformation ?? ParameterTransformationDefaults[parameter.type as ParameterSettingType] ?? undefined;
|
|
315
321
|
}
|
package/src/utils/utils.ts
CHANGED
|
@@ -114,6 +114,10 @@ export function areArraysEqual(
|
|
|
114
114
|
desired: unknown,
|
|
115
115
|
current: unknown
|
|
116
116
|
): boolean {
|
|
117
|
+
if (!desired || !current) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
117
121
|
if (!Array.isArray(desired) || !Array.isArray(current)) {
|
|
118
122
|
throw new Error(`A non-array value:
|
|
119
123
|
|