codify-plugin-lib 1.0.102 → 1.0.104

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 CHANGED
@@ -143,15 +143,28 @@ export class Plan {
143
143
  return Object.fromEntries(Object.entries(current)
144
144
  .filter(([k]) => keys.has(k)));
145
145
  }
146
+ function getFilterParameter(k) {
147
+ if (settings.parameterSettings?.[k]?.type === 'stateful') {
148
+ const statefulSetting = settings.parameterSettings[k];
149
+ if (statefulSetting.nestedSettings.type === 'array') {
150
+ return statefulSetting.nestedSettings.filterInStatelessMode;
151
+ }
152
+ }
153
+ if (settings.parameterSettings?.[k]?.type === 'array') {
154
+ return (settings.parameterSettings?.[k]).filterInStatelessMode;
155
+ }
156
+ return undefined;
157
+ }
146
158
  function isArrayParameterWithFiltering(k, v) {
159
+ const filterParameter = getFilterParameter(k);
147
160
  if (settings.parameterSettings?.[k]?.type === 'stateful') {
148
161
  const statefulSetting = settings.parameterSettings[k];
149
162
  return statefulSetting.nestedSettings.type === 'array' &&
150
- (statefulSetting.nestedSettings.filterInStatelessMode ?? true)
163
+ (filterParameter ?? true)
151
164
  && Array.isArray(v);
152
165
  }
153
166
  return settings.parameterSettings?.[k]?.type === 'array'
154
- && ((settings.parameterSettings?.[k]).filterInStatelessMode ?? true)
167
+ && (filterParameter ?? true)
155
168
  && Array.isArray(v);
156
169
  }
157
170
  // For stateless mode, we must filter the current array so that the diff algorithm will not detect any deletes
@@ -165,17 +178,23 @@ export class Plan {
165
178
  .isElementEqual;
166
179
  const desiredCopy = [...desiredArray];
167
180
  const currentCopy = [...v];
168
- const result = [];
169
- for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
170
- const idx = currentCopy.findIndex((e2) => matcher(desiredCopy[counter], e2));
171
- if (idx === -1) {
172
- continue;
181
+ const defaultFilterMethod = ((desired, current) => {
182
+ const result = [];
183
+ for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
184
+ const idx = currentCopy.findIndex((e2) => matcher(desiredCopy[counter], e2));
185
+ if (idx === -1) {
186
+ continue;
187
+ }
188
+ desiredCopy.splice(counter, 1);
189
+ const [element] = currentCopy.splice(idx, 1);
190
+ result.push(element);
173
191
  }
174
- desiredCopy.splice(counter, 1);
175
- const [element] = currentCopy.splice(idx, 1);
176
- result.push(element);
177
- }
178
- return result;
192
+ return result;
193
+ });
194
+ const filterParameter = getFilterParameter(k);
195
+ return typeof filterParameter === 'function'
196
+ ? filterParameter(desiredCopy, currentCopy)
197
+ : defaultFilterMethod(desiredCopy, currentCopy);
179
198
  }
180
199
  }
181
200
  // TODO: This needs to be revisited. I don't think this is valid anymore.
@@ -38,7 +38,7 @@ export class ResourceController {
38
38
  this.addDefaultValues(parameters);
39
39
  if (this.schemaValidator) {
40
40
  // Schema validator uses pre transformation parameters
41
- const isValid = this.schemaValidator(desiredConfig);
41
+ const isValid = this.schemaValidator(splitUserConfig(desiredConfig).parameters);
42
42
  if (!isValid) {
43
43
  return {
44
44
  isValid: false,
@@ -191,7 +191,7 @@ export interface ArrayParameterSetting extends DefaultParameterSetting {
191
191
  *
192
192
  * Defaults to true.
193
193
  */
194
- filterInStatelessMode?: boolean;
194
+ filterInStatelessMode?: ((desired: any[], current: any[]) => any[]) | boolean;
195
195
  }
196
196
  /**
197
197
  * Stateful parameter type specific settings. A stateful parameter is a sub-resource that can hold its own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codify-plugin-lib",
3
- "version": "1.0.102",
3
+ "version": "1.0.104",
4
4
  "description": "Library plugin library",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
package/src/plan/plan.ts CHANGED
@@ -251,16 +251,34 @@ export class Plan<T extends StringIndexedObject> {
251
251
  ) as Partial<T>;
252
252
  }
253
253
 
254
+ function getFilterParameter(k: string): ((desired: any[], current: any[]) => any[]) | boolean | undefined {
255
+ if (settings.parameterSettings?.[k]?.type === 'stateful') {
256
+ const statefulSetting = settings.parameterSettings[k] as ParsedStatefulParameterSetting;
257
+
258
+ if (statefulSetting.nestedSettings.type === 'array') {
259
+ return (statefulSetting.nestedSettings as ArrayParameterSetting).filterInStatelessMode
260
+ }
261
+ }
262
+
263
+ if (settings.parameterSettings?.[k]?.type === 'array') {
264
+ return (settings.parameterSettings?.[k] as ArrayParameterSetting).filterInStatelessMode;
265
+ }
266
+
267
+ return undefined;
268
+ }
269
+
254
270
  function isArrayParameterWithFiltering(k: string, v: T[keyof T]): boolean {
271
+ const filterParameter = getFilterParameter(k);
272
+
255
273
  if (settings.parameterSettings?.[k]?.type === 'stateful') {
256
274
  const statefulSetting = settings.parameterSettings[k] as ParsedStatefulParameterSetting;
257
275
  return statefulSetting.nestedSettings.type === 'array' &&
258
- ((statefulSetting.nestedSettings as ArrayParameterSetting).filterInStatelessMode ?? true)
276
+ (filterParameter ?? true)
259
277
  && Array.isArray(v);
260
278
  }
261
279
 
262
280
  return settings.parameterSettings?.[k]?.type === 'array'
263
- && ((settings.parameterSettings?.[k] as ArrayParameterSetting).filterInStatelessMode ?? true)
281
+ && (filterParameter ?? true)
264
282
  && Array.isArray(v);
265
283
  }
266
284
 
@@ -276,21 +294,29 @@ export class Plan<T extends StringIndexedObject> {
276
294
 
277
295
  const desiredCopy = [...desiredArray];
278
296
  const currentCopy = [...v];
279
- const result = [];
280
297
 
281
- for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
282
- const idx = currentCopy.findIndex((e2) => matcher(desiredCopy[counter], e2))
298
+ const defaultFilterMethod = ((desired: any[], current: any[]) => {
299
+ const result = [];
300
+
301
+ for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
302
+ const idx = currentCopy.findIndex((e2) => matcher(desiredCopy[counter], e2))
283
303
 
284
- if (idx === -1) {
285
- continue;
304
+ if (idx === -1) {
305
+ continue;
306
+ }
307
+
308
+ desiredCopy.splice(counter, 1)
309
+ const [element] = currentCopy.splice(idx, 1)
310
+ result.push(element)
286
311
  }
287
312
 
288
- desiredCopy.splice(counter, 1)
289
- const [element] = currentCopy.splice(idx, 1)
290
- result.push(element)
291
- }
313
+ return result;
314
+ })
292
315
 
293
- return result;
316
+ const filterParameter = getFilterParameter(k);
317
+ return typeof filterParameter === 'function'
318
+ ? filterParameter(desiredCopy, currentCopy)
319
+ : defaultFilterMethod(desiredCopy, currentCopy);
294
320
  }
295
321
  }
296
322
 
@@ -64,7 +64,7 @@ export class ResourceController<T extends StringIndexedObject> {
64
64
 
65
65
  if (this.schemaValidator) {
66
66
  // Schema validator uses pre transformation parameters
67
- const isValid = this.schemaValidator(desiredConfig);
67
+ const isValid = this.schemaValidator(splitUserConfig(desiredConfig).parameters);
68
68
 
69
69
  if (!isValid) {
70
70
  return {
@@ -239,6 +239,94 @@ describe('Resource parameter tests', () => {
239
239
  })
240
240
  })
241
241
 
242
+ it('Can accept a custom filter function to filter in stateless mode', async () => {
243
+ const resource = new class extends TestResource {
244
+ getSettings(): ResourceSettings<TestConfig> {
245
+ return {
246
+ id: 'type',
247
+ parameterSettings: {
248
+ hosts: {
249
+ type: 'array',
250
+ isElementEqual: 'object',
251
+ filterInStatelessMode: (desired, current) => {
252
+ return current.filter((d) => desired.some((c) => d.Host === c.Host))
253
+ }
254
+ }
255
+ }
256
+ }
257
+ }
258
+
259
+ async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
260
+ return {
261
+ hosts: [
262
+ {
263
+ Host: '*',
264
+ AddKeysToAgent: 'yes',
265
+ IdentityFile: 'id_ed25519'
266
+ },
267
+ {
268
+ Host: 'github.com',
269
+ AddKeysToAgent: 'yes',
270
+ UseKeychain: 'yes',
271
+ IgnoreUnknown: 'UseKeychain',
272
+ IdentityFile: '~/.ssh/id_ed25519',
273
+ }
274
+ ]
275
+ }
276
+ }
277
+ }
278
+
279
+ const controller = new ResourceController(resource);
280
+ const plan = await controller.plan({
281
+ type: 'type',
282
+ hosts: [
283
+ {
284
+ Host: 'new.com',
285
+ AddKeysToAgent: 'yes',
286
+ IdentityFile: '~/.ssh/id_ed25519'
287
+ },
288
+ {
289
+ Host: 'github.com',
290
+ AddKeysToAgent: 'yes',
291
+ UseKeychain: 'yes',
292
+ }
293
+ ]
294
+ });
295
+
296
+ expect(plan).toMatchObject({
297
+ 'changeSet': {
298
+ 'operation': 'recreate',
299
+ 'parameterChanges': [
300
+ {
301
+ 'name': 'hosts',
302
+ 'previousValue': [
303
+ {
304
+ 'Host': 'github.com',
305
+ 'AddKeysToAgent': 'yes',
306
+ 'UseKeychain': 'yes',
307
+ 'IgnoreUnknown': 'UseKeychain',
308
+ 'IdentityFile': '~/.ssh/id_ed25519'
309
+ }
310
+ ],
311
+ 'newValue': [
312
+ {
313
+ 'Host': 'new.com',
314
+ 'AddKeysToAgent': 'yes',
315
+ 'IdentityFile': '~/.ssh/id_ed25519'
316
+ },
317
+ {
318
+ 'Host': 'github.com',
319
+ 'AddKeysToAgent': 'yes',
320
+ 'UseKeychain': 'yes'
321
+ }
322
+ ],
323
+ 'operation': 'modify'
324
+ }
325
+ ]
326
+ },
327
+ })
328
+ })
329
+
242
330
  it('Uses isElementEqual for stateless mode filtering if available', async () => {
243
331
  const statefulParameter = new class extends TestArrayStatefulParameter {
244
332
  getSettings(): ArrayParameterSetting {
@@ -232,7 +232,7 @@ export interface ArrayParameterSetting extends DefaultParameterSetting {
232
232
  *
233
233
  * Defaults to true.
234
234
  */
235
- filterInStatelessMode?: boolean,
235
+ filterInStatelessMode?: ((desired: any[], current: any[]) => any[]) | boolean,
236
236
  }
237
237
 
238
238
  /**