codify-plugin-lib 1.0.135 → 1.0.136
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/resource/parsed-resource-settings.d.ts +1 -0
- package/dist/resource/parsed-resource-settings.js +2 -0
- package/dist/resource/resource-controller.js +7 -0
- package/dist/resource/resource-settings.d.ts +8 -1
- package/dist/resource/resource-settings.js +5 -5
- package/package.json +1 -1
- package/src/resource/parsed-resource-settings.ts +2 -0
- package/src/resource/resource-controller.test.ts +92 -2
- package/src/resource/resource-controller.ts +9 -0
- package/src/resource/resource-settings.test.ts +82 -2
- package/src/resource/resource-settings.ts +14 -6
|
@@ -25,6 +25,7 @@ export declare class ParsedResourceSettings<T extends StringIndexedObject> imple
|
|
|
25
25
|
} | boolean;
|
|
26
26
|
removeStatefulParametersBeforeDestroy?: boolean | undefined;
|
|
27
27
|
dependencies?: string[] | undefined;
|
|
28
|
+
transformation?: InputTransformation;
|
|
28
29
|
private settings;
|
|
29
30
|
constructor(settings: ResourceSettings<T>);
|
|
30
31
|
get typeId(): string;
|
|
@@ -7,6 +7,7 @@ export class ParsedResourceSettings {
|
|
|
7
7
|
allowMultiple;
|
|
8
8
|
removeStatefulParametersBeforeDestroy;
|
|
9
9
|
dependencies;
|
|
10
|
+
transformation;
|
|
10
11
|
settings;
|
|
11
12
|
constructor(settings) {
|
|
12
13
|
this.settings = settings;
|
|
@@ -15,6 +16,7 @@ export class ParsedResourceSettings {
|
|
|
15
16
|
this.allowMultiple = settings.allowMultiple;
|
|
16
17
|
this.removeStatefulParametersBeforeDestroy = settings.removeStatefulParametersBeforeDestroy;
|
|
17
18
|
this.dependencies = settings.dependencies;
|
|
19
|
+
this.transformation = settings.transformation;
|
|
18
20
|
this.validateSettings();
|
|
19
21
|
}
|
|
20
22
|
get typeId() {
|
|
@@ -250,6 +250,13 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
250
250
|
? await inputTransformation.from(config[key])
|
|
251
251
|
: await inputTransformation.to(config[key]);
|
|
252
252
|
}
|
|
253
|
+
if (this.settings.transformation) {
|
|
254
|
+
const transformed = reverse
|
|
255
|
+
? await this.settings.transformation.from({ ...config })
|
|
256
|
+
: await this.settings.transformation.to({ ...config });
|
|
257
|
+
Object.keys(config).forEach((k) => delete config[k]);
|
|
258
|
+
Object.assign(config, transformed);
|
|
259
|
+
}
|
|
253
260
|
}
|
|
254
261
|
addDefaultValues(config) {
|
|
255
262
|
if (!config) {
|
|
@@ -60,6 +60,13 @@ export interface ResourceSettings<T extends StringIndexedObject> {
|
|
|
60
60
|
* and applying any input transformations. Use parameter settings to define stateful parameters as well.
|
|
61
61
|
*/
|
|
62
62
|
parameterSettings?: Partial<Record<keyof T, ParameterSetting>>;
|
|
63
|
+
/**
|
|
64
|
+
* A config level transformation that is only applied to the user supplied desired config. This transformation is allowed
|
|
65
|
+
* to add, remove or modify keys as well as values. Changing this transformation for existing libraries will mess up existing states.
|
|
66
|
+
*
|
|
67
|
+
* @param desired
|
|
68
|
+
*/
|
|
69
|
+
transformation?: InputTransformation;
|
|
63
70
|
/**
|
|
64
71
|
* Customize the import and destory behavior of the resource. By default, <code>codify import</code> and <code>codify destroy</code> will call
|
|
65
72
|
* `refresh()` with every parameter set to null and return the result of the refresh as the imported config. It looks for required parameters
|
|
@@ -144,7 +151,7 @@ export interface DefaultParameterSetting {
|
|
|
144
151
|
*
|
|
145
152
|
* @param input The original parameter value from the desired config.
|
|
146
153
|
*/
|
|
147
|
-
|
|
154
|
+
transformation?: InputTransformation;
|
|
148
155
|
/**
|
|
149
156
|
* Customize the equality comparison for a parameter. This is used in the diffing algorithm for generating the plan.
|
|
150
157
|
* This value will override the pre-set equality function from the type. Return true if the desired value is
|
|
@@ -54,17 +54,17 @@ const ParameterTransformationDefaults = {
|
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
export function resolveParameterTransformFn(parameter) {
|
|
57
|
-
if (parameter.type === 'stateful' && !parameter.
|
|
57
|
+
if (parameter.type === 'stateful' && !parameter.transformation) {
|
|
58
58
|
const sp = parameter.definition.getSettings();
|
|
59
|
-
if (sp.
|
|
60
|
-
return parameter.definition?.getSettings()?.
|
|
59
|
+
if (sp.transformation) {
|
|
60
|
+
return parameter.definition?.getSettings()?.transformation;
|
|
61
61
|
}
|
|
62
62
|
return sp.type ? ParameterTransformationDefaults[sp.type] : undefined;
|
|
63
63
|
}
|
|
64
64
|
if (parameter.type === 'array'
|
|
65
65
|
&& parameter.itemType
|
|
66
66
|
&& ParameterTransformationDefaults[parameter.itemType]
|
|
67
|
-
&& !parameter.
|
|
67
|
+
&& !parameter.transformation) {
|
|
68
68
|
const itemType = parameter.itemType;
|
|
69
69
|
const itemTransformation = ParameterTransformationDefaults[itemType];
|
|
70
70
|
return {
|
|
@@ -76,5 +76,5 @@ export function resolveParameterTransformFn(parameter) {
|
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
return parameter.
|
|
79
|
+
return parameter.transformation ?? ParameterTransformationDefaults[parameter.type] ?? undefined;
|
|
80
80
|
}
|
package/package.json
CHANGED
|
@@ -44,6 +44,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
|
|
|
44
44
|
|
|
45
45
|
removeStatefulParametersBeforeDestroy?: boolean | undefined;
|
|
46
46
|
dependencies?: string[] | undefined;
|
|
47
|
+
transformation?: InputTransformation;
|
|
47
48
|
private settings: ResourceSettings<T>;
|
|
48
49
|
|
|
49
50
|
constructor(settings: ResourceSettings<T>) {
|
|
@@ -53,6 +54,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
|
|
|
53
54
|
this.allowMultiple = settings.allowMultiple;
|
|
54
55
|
this.removeStatefulParametersBeforeDestroy = settings.removeStatefulParametersBeforeDestroy;
|
|
55
56
|
this.dependencies = settings.dependencies;
|
|
57
|
+
this.transformation = settings.transformation;
|
|
56
58
|
|
|
57
59
|
this.validateSettings();
|
|
58
60
|
}
|
|
@@ -20,7 +20,7 @@ describe('Resource tests', () => {
|
|
|
20
20
|
id: 'type',
|
|
21
21
|
dependencies: ['homebrew', 'python'],
|
|
22
22
|
parameterSettings: {
|
|
23
|
-
propA: { canModify: true,
|
|
23
|
+
propA: { canModify: true, transformation: { to: (input) => untildify(input), from: (input) => tildify(input) } },
|
|
24
24
|
},
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -543,7 +543,7 @@ describe('Resource tests', () => {
|
|
|
543
543
|
parameterSettings: {
|
|
544
544
|
propD: {
|
|
545
545
|
type: 'array',
|
|
546
|
-
|
|
546
|
+
transformation: {
|
|
547
547
|
to: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
|
|
548
548
|
Object.entries(h)
|
|
549
549
|
.map(([k, v]) => [
|
|
@@ -618,4 +618,94 @@ describe('Resource tests', () => {
|
|
|
618
618
|
}
|
|
619
619
|
})
|
|
620
620
|
})
|
|
621
|
+
|
|
622
|
+
it('Applies reverse input transformations for imports (object level)', async () => {
|
|
623
|
+
const resource = new class extends TestResource {
|
|
624
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
625
|
+
return {
|
|
626
|
+
id: 'resourceType',
|
|
627
|
+
parameterSettings: {
|
|
628
|
+
propD: {
|
|
629
|
+
type: 'array',
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
transformation: {
|
|
633
|
+
to: (input: any) => ({
|
|
634
|
+
...input,
|
|
635
|
+
propD: input.propD?.map((h) => Object.fromEntries(
|
|
636
|
+
Object.entries(h)
|
|
637
|
+
.map(([k, v]) => [
|
|
638
|
+
k,
|
|
639
|
+
typeof v === 'boolean'
|
|
640
|
+
? (v ? 'yes' : 'no') // The file takes 'yes' or 'no' instead of booleans
|
|
641
|
+
: v,
|
|
642
|
+
])
|
|
643
|
+
)
|
|
644
|
+
)
|
|
645
|
+
}),
|
|
646
|
+
from: (output: any) => ({
|
|
647
|
+
...output,
|
|
648
|
+
propD: output.propD?.map((h) => Object.fromEntries(
|
|
649
|
+
Object.entries(h)
|
|
650
|
+
.map(([k, v]) => [
|
|
651
|
+
k,
|
|
652
|
+
v === 'yes' || v === 'no'
|
|
653
|
+
? (v === 'yes')
|
|
654
|
+
: v,
|
|
655
|
+
])
|
|
656
|
+
))
|
|
657
|
+
})
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
663
|
+
return {
|
|
664
|
+
propD: [
|
|
665
|
+
{
|
|
666
|
+
Host: 'new.com',
|
|
667
|
+
AddKeysToAgent: true,
|
|
668
|
+
IdentityFile: 'id_ed25519'
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
Host: 'github.com',
|
|
672
|
+
AddKeysToAgent: true,
|
|
673
|
+
UseKeychain: true,
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
Match: 'User bob,joe,phil',
|
|
677
|
+
PasswordAuthentication: true,
|
|
678
|
+
}
|
|
679
|
+
],
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const controller = new ResourceController(resource);
|
|
685
|
+
const plan = await controller.import({ type: 'resourceType' }, {});
|
|
686
|
+
|
|
687
|
+
expect(plan![0]).toMatchObject({
|
|
688
|
+
'core': {
|
|
689
|
+
'type': 'resourceType'
|
|
690
|
+
},
|
|
691
|
+
'parameters': {
|
|
692
|
+
'propD': [
|
|
693
|
+
{
|
|
694
|
+
'Host': 'new.com',
|
|
695
|
+
'AddKeysToAgent': true,
|
|
696
|
+
'IdentityFile': 'id_ed25519'
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
'Host': 'github.com',
|
|
700
|
+
'AddKeysToAgent': true,
|
|
701
|
+
'UseKeychain': true
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
'Match': 'User bob,joe,phil',
|
|
705
|
+
'PasswordAuthentication': true
|
|
706
|
+
}
|
|
707
|
+
]
|
|
708
|
+
}
|
|
709
|
+
})
|
|
710
|
+
})
|
|
621
711
|
});
|
|
@@ -350,6 +350,15 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
350
350
|
? await inputTransformation.from(config[key])
|
|
351
351
|
: await inputTransformation.to(config[key]);
|
|
352
352
|
}
|
|
353
|
+
|
|
354
|
+
if (this.settings.transformation) {
|
|
355
|
+
const transformed = reverse
|
|
356
|
+
? await this.settings.transformation.from({ ...config })
|
|
357
|
+
: await this.settings.transformation.to({ ...config })
|
|
358
|
+
|
|
359
|
+
Object.keys(config).forEach((k) => delete config[k])
|
|
360
|
+
Object.assign(config, transformed);
|
|
361
|
+
}
|
|
353
362
|
}
|
|
354
363
|
|
|
355
364
|
private addDefaultValues(config: Partial<T> | null): void {
|
|
@@ -564,6 +564,86 @@ describe('Resource parameter tests', () => {
|
|
|
564
564
|
expect(timestampC).to.be.lessThan(timestampA as any);
|
|
565
565
|
})
|
|
566
566
|
|
|
567
|
+
it('Supports transform parameters', async () => {
|
|
568
|
+
const resource = spy(new class extends TestResource {
|
|
569
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
570
|
+
return {
|
|
571
|
+
id: 'resourceType',
|
|
572
|
+
transformation: {
|
|
573
|
+
to: (desired) => ({
|
|
574
|
+
propA: 'propA',
|
|
575
|
+
propB: 10,
|
|
576
|
+
}),
|
|
577
|
+
from: (current) => ({
|
|
578
|
+
propA: 'propA',
|
|
579
|
+
propB: 10,
|
|
580
|
+
})
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
586
|
+
return {
|
|
587
|
+
propA: 'propA',
|
|
588
|
+
propB: 10,
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
const controller = new ResourceController(resource);
|
|
594
|
+
const plan = await controller.plan({ type: 'resourceType' }, { propC: 'abc' } as any, null, false);
|
|
595
|
+
|
|
596
|
+
expect(resource.refresh.called).to.be.true;
|
|
597
|
+
expect(resource.refresh.getCall(0).firstArg['propA']).to.exist;
|
|
598
|
+
expect(resource.refresh.getCall(0).firstArg['propB']).to.exist;
|
|
599
|
+
expect(resource.refresh.getCall(0).firstArg['propC']).to.not.exist;
|
|
600
|
+
|
|
601
|
+
expect(plan.desiredConfig?.propA).to.eq('propA');
|
|
602
|
+
expect(plan.desiredConfig?.propB).to.eq(10);
|
|
603
|
+
expect(plan.desiredConfig?.propC).to.be.undefined;
|
|
604
|
+
|
|
605
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
it('Supports transform parameters for state parameters', async () => {
|
|
609
|
+
const resource = spy(new class extends TestResource {
|
|
610
|
+
getSettings(): ResourceSettings<TestConfig> {
|
|
611
|
+
return {
|
|
612
|
+
id: 'resourceType',
|
|
613
|
+
transformation: {
|
|
614
|
+
to: (desired) => ({
|
|
615
|
+
propA: 'propA',
|
|
616
|
+
propB: 10,
|
|
617
|
+
}),
|
|
618
|
+
from: (desired) => ({
|
|
619
|
+
propA: 'propA',
|
|
620
|
+
propB: 10,
|
|
621
|
+
})
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
async refresh(): Promise<Partial<TestConfig> | null> {
|
|
627
|
+
return {
|
|
628
|
+
propA: 'propA',
|
|
629
|
+
propB: 10,
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
const controller = new ResourceController(resource);
|
|
635
|
+
const plan = await controller.plan({ type: 'resourceType' }, null, { propC: 'abc' }, true);
|
|
636
|
+
|
|
637
|
+
expect(resource.refresh.called).to.be.true;
|
|
638
|
+
expect(resource.refresh.getCall(0).firstArg['propA']).to.exist;
|
|
639
|
+
expect(resource.refresh.getCall(0).firstArg['propB']).to.exist;
|
|
640
|
+
expect(resource.refresh.getCall(0).firstArg['propC']).to.not.exist;
|
|
641
|
+
|
|
642
|
+
expect(plan.currentConfig?.propA).to.eq('propA');
|
|
643
|
+
expect(plan.currentConfig?.propB).to.eq(10);
|
|
644
|
+
expect(plan.currentConfig?.propC).to.be.undefined;
|
|
645
|
+
})
|
|
646
|
+
|
|
567
647
|
it('Allows import required parameters customization', () => {
|
|
568
648
|
const resource = new class extends TestResource {
|
|
569
649
|
getSettings(): ResourceSettings<TestConfig> {
|
|
@@ -784,7 +864,7 @@ describe('Resource parameter tests', () => {
|
|
|
784
864
|
parameterSettings: {
|
|
785
865
|
propD: {
|
|
786
866
|
type: 'array',
|
|
787
|
-
|
|
867
|
+
transformation: {
|
|
788
868
|
to: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
|
|
789
869
|
Object.entries(h)
|
|
790
870
|
.map(([k, v]) => [
|
|
@@ -850,7 +930,7 @@ describe('Resource parameter tests', () => {
|
|
|
850
930
|
getSettings(): any {
|
|
851
931
|
return {
|
|
852
932
|
type: 'array',
|
|
853
|
-
|
|
933
|
+
transformation: {
|
|
854
934
|
to: (hosts: Record<string, unknown>[]) => hosts.map((h) => Object.fromEntries(
|
|
855
935
|
Object.entries(h)
|
|
856
936
|
.map(([k, v]) => [
|
|
@@ -75,6 +75,14 @@ export interface ResourceSettings<T extends StringIndexedObject> {
|
|
|
75
75
|
*/
|
|
76
76
|
parameterSettings?: Partial<Record<keyof T, ParameterSetting>>;
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* A config level transformation that is only applied to the user supplied desired config. This transformation is allowed
|
|
80
|
+
* to add, remove or modify keys as well as values. Changing this transformation for existing libraries will mess up existing states.
|
|
81
|
+
*
|
|
82
|
+
* @param desired
|
|
83
|
+
*/
|
|
84
|
+
transformation?: InputTransformation;
|
|
85
|
+
|
|
78
86
|
/**
|
|
79
87
|
* Customize the import and destory behavior of the resource. By default, <code>codify import</code> and <code>codify destroy</code> will call
|
|
80
88
|
* `refresh()` with every parameter set to null and return the result of the refresh as the imported config. It looks for required parameters
|
|
@@ -181,7 +189,7 @@ export interface DefaultParameterSetting {
|
|
|
181
189
|
*
|
|
182
190
|
* @param input The original parameter value from the desired config.
|
|
183
191
|
*/
|
|
184
|
-
|
|
192
|
+
transformation?: InputTransformation;
|
|
185
193
|
|
|
186
194
|
/**
|
|
187
195
|
* Customize the equality comparison for a parameter. This is used in the diffing algorithm for generating the plan.
|
|
@@ -346,10 +354,10 @@ export function resolveParameterTransformFn(
|
|
|
346
354
|
parameter: ParameterSetting
|
|
347
355
|
): InputTransformation | undefined {
|
|
348
356
|
|
|
349
|
-
if (parameter.type === 'stateful' && !parameter.
|
|
357
|
+
if (parameter.type === 'stateful' && !parameter.transformation) {
|
|
350
358
|
const sp = (parameter as StatefulParameterSetting).definition.getSettings();
|
|
351
|
-
if (sp.
|
|
352
|
-
return (parameter as StatefulParameterSetting).definition?.getSettings()?.
|
|
359
|
+
if (sp.transformation) {
|
|
360
|
+
return (parameter as StatefulParameterSetting).definition?.getSettings()?.transformation
|
|
353
361
|
}
|
|
354
362
|
|
|
355
363
|
return sp.type ? ParameterTransformationDefaults[sp.type] : undefined;
|
|
@@ -358,7 +366,7 @@ export function resolveParameterTransformFn(
|
|
|
358
366
|
if (parameter.type === 'array'
|
|
359
367
|
&& (parameter as ArrayParameterSetting).itemType
|
|
360
368
|
&& ParameterTransformationDefaults[(parameter as ArrayParameterSetting).itemType!]
|
|
361
|
-
&& !parameter.
|
|
369
|
+
&& !parameter.transformation
|
|
362
370
|
) {
|
|
363
371
|
const itemType = (parameter as ArrayParameterSetting).itemType!;
|
|
364
372
|
const itemTransformation = ParameterTransformationDefaults[itemType]!;
|
|
@@ -373,5 +381,5 @@ export function resolveParameterTransformFn(
|
|
|
373
381
|
}
|
|
374
382
|
}
|
|
375
383
|
|
|
376
|
-
return parameter.
|
|
384
|
+
return parameter.transformation ?? ParameterTransformationDefaults[parameter.type as ParameterSettingType] ?? undefined;
|
|
377
385
|
}
|