codify-plugin-lib 1.0.99 → 1.0.100
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/plan/plan.js +13 -8
- package/dist/resource/resource-settings.d.ts +63 -0
- package/package.json +1 -1
- package/src/plan/plan.test.ts +73 -0
- package/src/plan/plan.ts +16 -8
- package/src/resource/parsed-resource-settings.ts +3 -3
- package/src/resource/resource-settings.ts +65 -0
- package/src/resource/stateful-parameter.test.ts +0 -2
package/dist/plan/plan.js
CHANGED
|
@@ -126,7 +126,7 @@ export class Plan {
|
|
|
126
126
|
}
|
|
127
127
|
// TODO: Add object handling here in addition to arrays in the future
|
|
128
128
|
const arrayStatefulParameters = Object.fromEntries(Object.entries(filteredCurrent)
|
|
129
|
-
.filter(([k, v]) =>
|
|
129
|
+
.filter(([k, v]) => isArrayParameterWithFiltering(k, v))
|
|
130
130
|
.map(([k, v]) => [k, filterArrayStatefulParameter(k, v)]));
|
|
131
131
|
return { ...filteredCurrent, ...arrayStatefulParameters };
|
|
132
132
|
function filterCurrent() {
|
|
@@ -143,18 +143,23 @@ export class Plan {
|
|
|
143
143
|
return Object.fromEntries(Object.entries(current)
|
|
144
144
|
.filter(([k]) => keys.has(k)));
|
|
145
145
|
}
|
|
146
|
-
function
|
|
147
|
-
return settings.parameterSettings?.[k]?.type === 'stateful'
|
|
148
|
-
&& settings.parameterSettings[k].definition.getSettings().type === 'array'
|
|
146
|
+
function isArrayParameterWithFiltering(k, v) {
|
|
147
|
+
return (((settings.parameterSettings?.[k]?.type === 'stateful'
|
|
148
|
+
&& settings.parameterSettings[k].definition.getSettings().type === 'array')
|
|
149
|
+
&& (settings.parameterSettings[k].definition.getSettings().filterInStatelessMode ?? true)) || (settings.parameterSettings?.[k]?.type === 'array'
|
|
150
|
+
&& ((settings.parameterSettings?.[k]).filterInStatelessMode ?? true)))
|
|
149
151
|
&& Array.isArray(v);
|
|
150
152
|
}
|
|
151
153
|
// For stateless mode, we must filter the current array so that the diff algorithm will not detect any deletes
|
|
152
154
|
function filterArrayStatefulParameter(k, v) {
|
|
153
155
|
const desiredArray = desired[k];
|
|
154
|
-
const matcher = settings.parameterSettings[k]
|
|
155
|
-
.
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
const matcher = settings.parameterSettings[k].type === 'stateful'
|
|
157
|
+
? settings.parameterSettings[k]
|
|
158
|
+
.definition
|
|
159
|
+
.getSettings()
|
|
160
|
+
.isElementEqual ?? ((a, b) => a === b)
|
|
161
|
+
: settings.parameterSettings[k]
|
|
162
|
+
.isElementEqual ?? ((a, b) => a === b);
|
|
158
163
|
const desiredCopy = [...desiredArray];
|
|
159
164
|
const currentCopy = [...v];
|
|
160
165
|
const result = [];
|
|
@@ -52,9 +52,53 @@ export interface ResourceSettings<T extends StringIndexedObject> {
|
|
|
52
52
|
* @param desired
|
|
53
53
|
*/
|
|
54
54
|
inputTransformation?: (desired: Partial<T>) => Promise<unknown> | unknown;
|
|
55
|
+
/**
|
|
56
|
+
* Customize the import behavior of the resource. By default, <code>codify import</code> will call `refresh()` with
|
|
57
|
+
* every parameter set to null and return the result of the refresh as the imported config. It looks for required parameters
|
|
58
|
+
* in the schema and will prompt the user for these values before performing the import.
|
|
59
|
+
*
|
|
60
|
+
* <b>Example:</b><br>
|
|
61
|
+
* Resource `alias` with parameters
|
|
62
|
+
*
|
|
63
|
+
* ```
|
|
64
|
+
* { alias <b>(*required)</b>: string; value: string; }
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* When the user calls `codify import alias`, they will first be prompted to enter the value for `alias`. Refresh
|
|
68
|
+
* is then called with `refresh({ alias: 'user-input', value: null })`. The result returned to the user will then be:
|
|
69
|
+
*
|
|
70
|
+
* ```
|
|
71
|
+
* { type: 'alias', alias: 'user-input', value: 'git push' }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
55
74
|
import?: {
|
|
75
|
+
/**
|
|
76
|
+
* Customize the required parameters needed to import this resource. By default, the `requiredParameters` are taken
|
|
77
|
+
* from the JSON schema. The `requiredParameters` parameter must be declared if a complex required is declared in
|
|
78
|
+
* the schema (contains `oneOf`, `anyOf`, `allOf`, `if`, `then`, `else`).
|
|
79
|
+
* <br>
|
|
80
|
+
* The user will be prompted for the required parameters before the import starts. This is done because for most resources
|
|
81
|
+
* the required parameters change the behaviour of the refresh (for example for the `alias` resource, the `alias` parmaeter
|
|
82
|
+
* chooses which alias the resource is managing).
|
|
83
|
+
*
|
|
84
|
+
* See {@link import} for more information on how importing works.
|
|
85
|
+
*/
|
|
56
86
|
requiredParameters?: Array<Partial<keyof T>>;
|
|
87
|
+
/**
|
|
88
|
+
* Customize which keys will be refreshed in the import. Typically, `refresh()` statements only refresh
|
|
89
|
+
* the parameters provided as the input. Use `refreshKeys` to control which parameter keys are passed in.
|
|
90
|
+
* <br>
|
|
91
|
+
* By default all parameters (except for {@link requiredParameters }) are passed in with the value `null`. The passed
|
|
92
|
+
* in value can be customized using {@link defaultRefreshValues}
|
|
93
|
+
*
|
|
94
|
+
* See {@link import} for more information on how importing works.
|
|
95
|
+
*/
|
|
57
96
|
refreshKeys?: Array<Partial<keyof T>>;
|
|
97
|
+
/**
|
|
98
|
+
* Customize the value that is passed into refresh when importing. This must only contain keys found in {@link refreshKeys}.
|
|
99
|
+
*
|
|
100
|
+
* See {@link import} for more information on how importing works.
|
|
101
|
+
*/
|
|
58
102
|
defaultRefreshValues?: Partial<T>;
|
|
59
103
|
};
|
|
60
104
|
}
|
|
@@ -129,6 +173,25 @@ export interface ArrayParameterSetting extends DefaultParameterSetting {
|
|
|
129
173
|
* @return Return true if desired is equivalent to current.
|
|
130
174
|
*/
|
|
131
175
|
isElementEqual?: (desired: any, current: any) => boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Filter the contents of the refreshed array by the desired. This way items currently on the system but not
|
|
178
|
+
* in desired don't show up in the plan.
|
|
179
|
+
*
|
|
180
|
+
* <b>For example, for the nvm resource:</b>
|
|
181
|
+
* <ul>
|
|
182
|
+
* <li>Desired (20.18.0, 18.9.0, 16.3.1)</li>
|
|
183
|
+
* <li>Current (20.18.0, 22.1.3, 12.1.0)</li>
|
|
184
|
+
* </ul>
|
|
185
|
+
*
|
|
186
|
+
* Without filtering the plan will be:
|
|
187
|
+
* (~20.18.0, +18.9.0, +16.3.1, -22.1.3, -12.1.0)<br>
|
|
188
|
+
* With filtering the plan is: (~20.18.0, +18.9.0, +16.3.1)
|
|
189
|
+
*
|
|
190
|
+
* As you can see, filtering prevents items currently installed on the system from being removed.
|
|
191
|
+
*
|
|
192
|
+
* Defaults to true.
|
|
193
|
+
*/
|
|
194
|
+
filterInStatelessMode?: boolean;
|
|
132
195
|
}
|
|
133
196
|
/**
|
|
134
197
|
* Stateful parameter type specific settings. A stateful parameter is a sub-resource that can hold its own
|
package/package.json
CHANGED
package/src/plan/plan.test.ts
CHANGED
|
@@ -147,6 +147,79 @@ describe('Plan entity tests', () => {
|
|
|
147
147
|
operation: ResourceOperation.RECREATE
|
|
148
148
|
})
|
|
149
149
|
})
|
|
150
|
+
|
|
151
|
+
it('Filters array parameters in stateless mode (by default)', async () => {
|
|
152
|
+
const resource = new class extends TestResource {
|
|
153
|
+
getSettings(): ResourceSettings<any> {
|
|
154
|
+
return {
|
|
155
|
+
id: 'type',
|
|
156
|
+
parameterSettings: {
|
|
157
|
+
propZ: { type: 'array', isElementEqual: (a, b) => b.includes(a) }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async refresh(): Promise<Partial<any> | null> {
|
|
163
|
+
return {
|
|
164
|
+
propZ: [
|
|
165
|
+
'20.15.0',
|
|
166
|
+
'20.15.1'
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const controller = new ResourceController(resource);
|
|
173
|
+
const plan = await controller.plan({
|
|
174
|
+
propZ: ['20.15'],
|
|
175
|
+
} as any)
|
|
176
|
+
|
|
177
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('Doesn\'t filters array parameters if filtering is disabled', async () => {
|
|
181
|
+
const resource = new class extends TestResource {
|
|
182
|
+
getSettings(): ResourceSettings<any> {
|
|
183
|
+
return {
|
|
184
|
+
id: 'type',
|
|
185
|
+
parameterSettings: {
|
|
186
|
+
propZ: { type: 'array', canModify: true, isElementEqual: (a, b) => b.includes(a), filterInStatelessMode: false }
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async refresh(): Promise<Partial<any> | null> {
|
|
192
|
+
return {
|
|
193
|
+
propZ: [
|
|
194
|
+
'20.15.0',
|
|
195
|
+
'20.15.1'
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const controller = new ResourceController(resource);
|
|
202
|
+
const plan = await controller.plan({
|
|
203
|
+
propZ: ['20.15'],
|
|
204
|
+
} as any)
|
|
205
|
+
|
|
206
|
+
expect(plan.changeSet).toMatchObject({
|
|
207
|
+
operation: ResourceOperation.MODIFY,
|
|
208
|
+
parameterChanges: expect.arrayContaining([
|
|
209
|
+
expect.objectContaining({
|
|
210
|
+
name: 'propZ',
|
|
211
|
+
previousValue: expect.arrayContaining([
|
|
212
|
+
'20.15.0',
|
|
213
|
+
'20.15.1'
|
|
214
|
+
]),
|
|
215
|
+
newValue: expect.arrayContaining([
|
|
216
|
+
'20.15'
|
|
217
|
+
]),
|
|
218
|
+
operation: 'modify'
|
|
219
|
+
})
|
|
220
|
+
])
|
|
221
|
+
})
|
|
222
|
+
})
|
|
150
223
|
})
|
|
151
224
|
|
|
152
225
|
function createTestResource() {
|
package/src/plan/plan.ts
CHANGED
|
@@ -220,7 +220,7 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
220
220
|
// TODO: Add object handling here in addition to arrays in the future
|
|
221
221
|
const arrayStatefulParameters = Object.fromEntries(
|
|
222
222
|
Object.entries(filteredCurrent)
|
|
223
|
-
.filter(([k, v]) =>
|
|
223
|
+
.filter(([k, v]) => isArrayParameterWithFiltering(k, v))
|
|
224
224
|
.map(([k, v]) => [k, filterArrayStatefulParameter(k, v)])
|
|
225
225
|
)
|
|
226
226
|
|
|
@@ -247,19 +247,27 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
247
247
|
) as Partial<T>;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
function
|
|
251
|
-
return settings.parameterSettings?.[k]?.type === 'stateful'
|
|
252
|
-
|
|
250
|
+
function isArrayParameterWithFiltering(k: string, v: T[keyof T]): boolean {
|
|
251
|
+
return (((settings.parameterSettings?.[k]?.type === 'stateful'
|
|
252
|
+
&& (settings.parameterSettings[k] as StatefulParameterSetting).definition.getSettings().type === 'array')
|
|
253
|
+
&& (((settings.parameterSettings[k] as StatefulParameterSetting).definition.getSettings() as ArrayParameterSetting).filterInStatelessMode ?? true)
|
|
254
|
+
) || (
|
|
255
|
+
settings.parameterSettings?.[k]?.type === 'array'
|
|
256
|
+
&& ((settings.parameterSettings?.[k] as ArrayParameterSetting).filterInStatelessMode ?? true)
|
|
257
|
+
))
|
|
253
258
|
&& Array.isArray(v)
|
|
254
259
|
}
|
|
255
260
|
|
|
256
261
|
// For stateless mode, we must filter the current array so that the diff algorithm will not detect any deletes
|
|
257
262
|
function filterArrayStatefulParameter(k: string, v: unknown[]): unknown[] {
|
|
258
263
|
const desiredArray = desired![k] as unknown[];
|
|
259
|
-
const matcher =
|
|
260
|
-
.
|
|
261
|
-
|
|
262
|
-
|
|
264
|
+
const matcher = settings.parameterSettings![k]!.type === 'stateful'
|
|
265
|
+
? ((settings.parameterSettings![k] as StatefulParameterSetting)
|
|
266
|
+
.definition
|
|
267
|
+
.getSettings() as ArrayParameterSetting)
|
|
268
|
+
.isElementEqual ?? ((a, b) => a === b)
|
|
269
|
+
: (settings.parameterSettings![k] as ArrayParameterSetting)
|
|
270
|
+
.isElementEqual ?? ((a, b) => a === b)
|
|
263
271
|
|
|
264
272
|
const desiredCopy = [...desiredArray];
|
|
265
273
|
const currentCopy = [...v];
|
|
@@ -171,9 +171,9 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
|
|
|
171
171
|
const { requiredParameters, refreshKeys, defaultRefreshValues } = this.settings.import;
|
|
172
172
|
|
|
173
173
|
const requiredParametersNotInSchema = requiredParameters
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
?.filter(
|
|
175
|
+
(p) => schema && !(schema.properties[p])
|
|
176
|
+
)
|
|
177
177
|
if (schema && requiredParametersNotInSchema && requiredParametersNotInSchema.length > 0) {
|
|
178
178
|
throw new Error(`The following properties were declared in settings.import.requiredParameters but were not found in the schema:
|
|
179
179
|
${JSON.stringify(requiredParametersNotInSchema, null, 2)}`)
|
|
@@ -65,11 +65,56 @@ export interface ResourceSettings<T extends StringIndexedObject> {
|
|
|
65
65
|
*/
|
|
66
66
|
inputTransformation?: (desired: Partial<T>) => Promise<unknown> | unknown;
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Customize the import behavior of the resource. By default, <code>codify import</code> will call `refresh()` with
|
|
70
|
+
* every parameter set to null and return the result of the refresh as the imported config. It looks for required parameters
|
|
71
|
+
* in the schema and will prompt the user for these values before performing the import.
|
|
72
|
+
*
|
|
73
|
+
* <b>Example:</b><br>
|
|
74
|
+
* Resource `alias` with parameters
|
|
75
|
+
*
|
|
76
|
+
* ```
|
|
77
|
+
* { alias <b>(*required)</b>: string; value: string; }
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* When the user calls `codify import alias`, they will first be prompted to enter the value for `alias`. Refresh
|
|
81
|
+
* is then called with `refresh({ alias: 'user-input', value: null })`. The result returned to the user will then be:
|
|
82
|
+
*
|
|
83
|
+
* ```
|
|
84
|
+
* { type: 'alias', alias: 'user-input', value: 'git push' }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
68
87
|
import?: {
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Customize the required parameters needed to import this resource. By default, the `requiredParameters` are taken
|
|
91
|
+
* from the JSON schema. The `requiredParameters` parameter must be declared if a complex required is declared in
|
|
92
|
+
* the schema (contains `oneOf`, `anyOf`, `allOf`, `if`, `then`, `else`).
|
|
93
|
+
* <br>
|
|
94
|
+
* The user will be prompted for the required parameters before the import starts. This is done because for most resources
|
|
95
|
+
* the required parameters change the behaviour of the refresh (for example for the `alias` resource, the `alias` parmaeter
|
|
96
|
+
* chooses which alias the resource is managing).
|
|
97
|
+
*
|
|
98
|
+
* See {@link import} for more information on how importing works.
|
|
99
|
+
*/
|
|
69
100
|
requiredParameters?: Array<Partial<keyof T>>;
|
|
70
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Customize which keys will be refreshed in the import. Typically, `refresh()` statements only refresh
|
|
104
|
+
* the parameters provided as the input. Use `refreshKeys` to control which parameter keys are passed in.
|
|
105
|
+
* <br>
|
|
106
|
+
* By default all parameters (except for {@link requiredParameters }) are passed in with the value `null`. The passed
|
|
107
|
+
* in value can be customized using {@link defaultRefreshValues}
|
|
108
|
+
*
|
|
109
|
+
* See {@link import} for more information on how importing works.
|
|
110
|
+
*/
|
|
71
111
|
refreshKeys?: Array<Partial<keyof T>>;
|
|
72
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Customize the value that is passed into refresh when importing. This must only contain keys found in {@link refreshKeys}.
|
|
115
|
+
*
|
|
116
|
+
* See {@link import} for more information on how importing works.
|
|
117
|
+
*/
|
|
73
118
|
defaultRefreshValues?: Partial<T>
|
|
74
119
|
}
|
|
75
120
|
}
|
|
@@ -166,6 +211,26 @@ export interface ArrayParameterSetting extends DefaultParameterSetting {
|
|
|
166
211
|
* @return Return true if desired is equivalent to current.
|
|
167
212
|
*/
|
|
168
213
|
isElementEqual?: (desired: any, current: any) => boolean
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Filter the contents of the refreshed array by the desired. This way items currently on the system but not
|
|
217
|
+
* in desired don't show up in the plan.
|
|
218
|
+
*
|
|
219
|
+
* <b>For example, for the nvm resource:</b>
|
|
220
|
+
* <ul>
|
|
221
|
+
* <li>Desired (20.18.0, 18.9.0, 16.3.1)</li>
|
|
222
|
+
* <li>Current (20.18.0, 22.1.3, 12.1.0)</li>
|
|
223
|
+
* </ul>
|
|
224
|
+
*
|
|
225
|
+
* Without filtering the plan will be:
|
|
226
|
+
* (~20.18.0, +18.9.0, +16.3.1, -22.1.3, -12.1.0)<br>
|
|
227
|
+
* With filtering the plan is: (~20.18.0, +18.9.0, +16.3.1)
|
|
228
|
+
*
|
|
229
|
+
* As you can see, filtering prevents items currently installed on the system from being removed.
|
|
230
|
+
*
|
|
231
|
+
* Defaults to true.
|
|
232
|
+
*/
|
|
233
|
+
filterInStatelessMode?: boolean,
|
|
169
234
|
}
|
|
170
235
|
|
|
171
236
|
/**
|