codify-plugin-lib 1.0.128 → 1.0.130

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.
@@ -25,9 +25,11 @@ describe('Message handler tests', () => {
25
25
  await handler.onMessage({
26
26
  cmd: 'plan',
27
27
  data: {
28
- desired: {
28
+ core: {
29
29
  type: 'resourceType',
30
30
  name: 'name',
31
+ },
32
+ desired: {
31
33
  prop1: 'A',
32
34
  prop2: 'B',
33
35
  },
@@ -45,7 +47,6 @@ describe('Message handler tests', () => {
45
47
  const handler = new MessageHandler(plugin);
46
48
 
47
49
  process.send = (message) => {
48
- console.log(message);
49
50
  expect(message).toMatchObject({
50
51
  cmd: 'plan_Response',
51
52
  status: MessageStatus.ERROR,
@@ -168,6 +169,9 @@ describe('Message handler tests', () => {
168
169
  expect(async () => await handler.onMessage({
169
170
  cmd: 'plan',
170
171
  data: {
172
+ core: {
173
+ type: 'resourceA',
174
+ },
171
175
  desired: {
172
176
  type: 'resourceA'
173
177
  },
@@ -250,9 +254,11 @@ describe('Message handler tests', () => {
250
254
  cmd: 'plan',
251
255
  requestId: 'abcdef',
252
256
  data: {
253
- desired: {
257
+ core: {
254
258
  type: 'type',
255
259
  name: 'name',
260
+ },
261
+ desired: {
256
262
  prop1: 'A',
257
263
  prop2: 'B',
258
264
  },
@@ -20,13 +20,12 @@ describe('Plan entity tests', () => {
20
20
  previousValue: null,
21
21
  newValue: 'propBValue'
22
22
  }],
23
- statefulMode: false,
23
+ isStateful: false,
24
24
  }, controller.parsedSettings.defaultValues);
25
25
 
26
26
  expect(plan.currentConfig).to.be.null;
27
27
 
28
28
  expect(plan.desiredConfig).toMatchObject({
29
- type: 'type',
30
29
  propA: 'defaultA',
31
30
  propB: 'propBValue',
32
31
  })
@@ -49,11 +48,10 @@ describe('Plan entity tests', () => {
49
48
  previousValue: 'propBValue',
50
49
  newValue: null,
51
50
  }],
52
- statefulMode: false,
51
+ isStateful: false,
53
52
  }, controller.parsedSettings.defaultValues);
54
53
 
55
54
  expect(plan.currentConfig).toMatchObject({
56
- type: 'type',
57
55
  propA: 'defaultA',
58
56
  propB: 'propBValue',
59
57
  })
@@ -78,17 +76,15 @@ describe('Plan entity tests', () => {
78
76
  previousValue: 'propBValue',
79
77
  newValue: 'propBValue',
80
78
  }],
81
- statefulMode: false,
79
+ isStateful: false,
82
80
  }, controller.parsedSettings.defaultValues);
83
81
 
84
82
  expect(plan.currentConfig).toMatchObject({
85
- type: 'type',
86
83
  propA: 'defaultA',
87
84
  propB: 'propBValue',
88
85
  })
89
86
 
90
87
  expect(plan.desiredConfig).toMatchObject({
91
- type: 'type',
92
88
  propA: 'defaultA',
93
89
  propB: 'propBValue',
94
90
  })
@@ -116,13 +112,12 @@ describe('Plan entity tests', () => {
116
112
  previousValue: null,
117
113
  newValue: 'propAValue',
118
114
  }],
119
- statefulMode: false,
115
+ isStateful: false,
120
116
  }, controller.parsedSettings.defaultValues);
121
117
 
122
118
  expect(plan.currentConfig).to.be.null
123
119
 
124
120
  expect(plan.desiredConfig).toMatchObject({
125
- type: 'type',
126
121
  propA: 'propAValue',
127
122
  propB: 'propBValue',
128
123
  })
@@ -134,15 +129,15 @@ describe('Plan entity tests', () => {
134
129
 
135
130
  it('Returns the original resource names', () => {
136
131
  const plan = Plan.calculate<TestConfig>({
137
- desiredParameters: { propA: 'propA' },
138
- currentParametersArray: [{ propA: 'propA2' }],
139
- stateParameters: null,
140
- coreParameters: {
132
+ desired: { propA: 'propA' },
133
+ currentArray: [{ propA: 'propA2' }],
134
+ state: null,
135
+ core: {
141
136
  type: 'type',
142
137
  name: 'name1'
143
138
  },
144
139
  settings: new ParsedResourceSettings<TestConfig>({ id: 'type' }),
145
- statefulMode: false,
140
+ isStateful: false,
146
141
  });
147
142
 
148
143
  expect(plan.toResponse()).toMatchObject({
@@ -174,9 +169,12 @@ describe('Plan entity tests', () => {
174
169
  }
175
170
 
176
171
  const controller = new ResourceController(resource);
177
- const plan = await controller.plan({
178
- propZ: ['20.15'],
179
- } as any)
172
+ const plan = await controller.plan(
173
+ { type: 'type' },
174
+ { propZ: ['20.15'], } as any,
175
+ null,
176
+ false
177
+ )
180
178
 
181
179
  expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
182
180
  })
@@ -208,9 +206,12 @@ describe('Plan entity tests', () => {
208
206
  }
209
207
 
210
208
  const controller = new ResourceController(resource);
211
- const plan = await controller.plan({
212
- propZ: ['20.15'],
213
- } as any)
209
+ const plan = await controller.plan(
210
+ { type: 'type' },
211
+ { propZ: ['20.15'], } as any,
212
+ null,
213
+ false
214
+ )
214
215
 
215
216
  expect(plan.changeSet).toMatchObject({
216
217
  operation: ResourceOperation.MODIFY,
package/src/plan/plan.ts CHANGED
@@ -34,13 +34,13 @@ export class Plan<T extends StringIndexedObject> {
34
34
  */
35
35
  coreParameters: ResourceConfig;
36
36
 
37
- statefulMode: boolean;
37
+ isStateful: boolean;
38
38
 
39
- constructor(id: string, changeSet: ChangeSet<T>, resourceMetadata: ResourceConfig, statefulMode: boolean) {
39
+ constructor(id: string, changeSet: ChangeSet<T>, coreParameters: ResourceConfig, isStateful: boolean) {
40
40
  this.id = id;
41
41
  this.changeSet = changeSet;
42
- this.coreParameters = resourceMetadata;
43
- this.statefulMode = statefulMode;
42
+ this.coreParameters = coreParameters;
43
+ this.isStateful = isStateful;
44
44
  }
45
45
 
46
46
  /**
@@ -51,10 +51,7 @@ export class Plan<T extends StringIndexedObject> {
51
51
  return null;
52
52
  }
53
53
 
54
- return {
55
- ...this.coreParameters,
56
- ...this.changeSet.desiredParameters,
57
- }
54
+ return this.changeSet.desiredParameters;
58
55
  }
59
56
 
60
57
  /**
@@ -65,10 +62,7 @@ export class Plan<T extends StringIndexedObject> {
65
62
  return null;
66
63
  }
67
64
 
68
- return {
69
- ...this.coreParameters,
70
- ...this.changeSet.currentParameters,
71
- }
65
+ return this.changeSet.currentParameters;
72
66
  }
73
67
 
74
68
  get resourceId(): string {
@@ -78,71 +72,71 @@ export class Plan<T extends StringIndexedObject> {
78
72
  }
79
73
 
80
74
  static calculate<T extends StringIndexedObject>(params: {
81
- desiredParameters: Partial<T> | null,
82
- currentParametersArray: Partial<T>[] | null,
83
- stateParameters: Partial<T> | null,
84
- coreParameters: ResourceConfig,
75
+ desired: Partial<T> | null,
76
+ currentArray: Partial<T>[] | null,
77
+ state: Partial<T> | null,
78
+ core: ResourceConfig,
85
79
  settings: ParsedResourceSettings<T>,
86
- statefulMode: boolean,
80
+ isStateful: boolean,
87
81
  }): Plan<T> {
88
82
  const {
89
- desiredParameters,
90
- currentParametersArray,
91
- stateParameters,
92
- coreParameters,
83
+ desired,
84
+ currentArray,
85
+ state,
86
+ core,
93
87
  settings,
94
- statefulMode
88
+ isStateful
95
89
  } = params
96
90
 
97
- const currentParameters = Plan.matchCurrentParameters<T>({
98
- desiredParameters,
99
- currentParametersArray,
100
- stateParameters,
91
+ const current = Plan.matchCurrentParameters<T>({
92
+ desired,
93
+ currentArray,
94
+ state,
101
95
  settings,
102
- statefulMode
96
+ isStateful
103
97
  });
104
98
 
105
99
  const filteredCurrentParameters = Plan.filterCurrentParams<T>({
106
- desiredParameters,
107
- currentParameters,
108
- stateParameters,
100
+ desired,
101
+ current,
102
+ state,
109
103
  settings,
110
- statefulMode
104
+ isStateful
111
105
  });
112
106
 
113
107
  // Empty
114
- if (!filteredCurrentParameters && !desiredParameters) {
108
+ if (!filteredCurrentParameters && !desired) {
115
109
  return new Plan(
116
110
  uuidV4(),
117
111
  ChangeSet.empty<T>(),
118
- coreParameters,
119
- statefulMode,
112
+ core,
113
+ isStateful,
120
114
  )
121
115
  }
122
116
 
123
117
  // CREATE
124
- if (!filteredCurrentParameters && desiredParameters) {
118
+ if (!filteredCurrentParameters && desired) {
125
119
  return new Plan(
126
120
  uuidV4(),
127
- ChangeSet.create(desiredParameters),
128
- coreParameters,
129
- statefulMode,
121
+ ChangeSet.create(desired),
122
+ core,
123
+ isStateful,
130
124
  )
131
125
  }
132
126
 
133
127
  // DESTROY
134
- if (filteredCurrentParameters && !desiredParameters) {
128
+ if (filteredCurrentParameters && !desired) {
135
129
  return new Plan(
136
130
  uuidV4(),
137
131
  ChangeSet.destroy(filteredCurrentParameters),
138
- coreParameters,
139
- statefulMode,
132
+ core,
133
+ isStateful,
140
134
  )
141
135
  }
142
136
 
143
137
  // NO-OP, MODIFY or RE-CREATE
144
138
  const changeSet = ChangeSet.calculateModification(
145
- desiredParameters!,
139
+ desired!,
146
140
  filteredCurrentParameters!,
147
141
  settings.parameterSettings,
148
142
  );
@@ -150,60 +144,11 @@ export class Plan<T extends StringIndexedObject> {
150
144
  return new Plan(
151
145
  uuidV4(),
152
146
  changeSet,
153
- coreParameters,
154
- statefulMode,
147
+ core,
148
+ isStateful,
155
149
  );
156
150
  }
157
151
 
158
- /**
159
- * When multiples of the same resource are allowed, this matching function will match a given config with one of the
160
- * existing configs on the system. For example if there are multiple versions of Android Studios installed, we can use
161
- * the application name and location to match it to our desired configs name and location.
162
- *
163
- * @param params
164
- * @private
165
- */
166
- private static matchCurrentParameters<T extends StringIndexedObject>(params: {
167
- desiredParameters: Partial<T> | null,
168
- currentParametersArray: Partial<T>[] | null,
169
- stateParameters: Partial<T> | null,
170
- settings: ResourceSettings<T>,
171
- statefulMode: boolean,
172
- }): Partial<T> | null {
173
- const {
174
- desiredParameters,
175
- currentParametersArray,
176
- stateParameters,
177
- settings,
178
- statefulMode
179
- } = params;
180
-
181
- if (!settings.allowMultiple) {
182
- return currentParametersArray?.[0] ?? null;
183
- }
184
-
185
- if (!currentParametersArray) {
186
- return null;
187
- }
188
-
189
- if (statefulMode) {
190
- return stateParameters
191
- ? settings.allowMultiple.matcher(stateParameters, currentParametersArray)
192
- : null
193
- }
194
-
195
- return settings.allowMultiple.matcher(desiredParameters!, currentParametersArray);
196
- }
197
-
198
- /**
199
- * The type (id) of the resource
200
- *
201
- * @return string
202
- */
203
- getResourceType(): string {
204
- return this.coreParameters.type
205
- }
206
-
207
152
  // 2. Even if there was (maybe for testing reasons), the plan values should not be adjusted
208
153
  static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues?: Partial<Record<keyof T, unknown>>): Plan<T> {
209
154
  if (!data) {
@@ -222,7 +167,7 @@ export class Plan<T extends StringIndexedObject> {
222
167
  type: data.resourceType,
223
168
  name: data.resourceName,
224
169
  },
225
- data.statefulMode
170
+ data.isStateful
226
171
  );
227
172
 
228
173
  function addDefaultValues(): void {
@@ -275,6 +220,55 @@ export class Plan<T extends StringIndexedObject> {
275
220
 
276
221
  }
277
222
 
223
+ /**
224
+ * The type (id) of the resource
225
+ *
226
+ * @return string
227
+ */
228
+ getResourceType(): string {
229
+ return this.coreParameters.type
230
+ }
231
+
232
+ /**
233
+ * When multiples of the same resource are allowed, this matching function will match a given config with one of the
234
+ * existing configs on the system. For example if there are multiple versions of Android Studios installed, we can use
235
+ * the application name and location to match it to our desired configs name and location.
236
+ *
237
+ * @param params
238
+ * @private
239
+ */
240
+ private static matchCurrentParameters<T extends StringIndexedObject>(params: {
241
+ desired: Partial<T> | null,
242
+ currentArray: Partial<T>[] | null,
243
+ state: Partial<T> | null,
244
+ settings: ResourceSettings<T>,
245
+ isStateful: boolean,
246
+ }): Partial<T> | null {
247
+ const {
248
+ desired,
249
+ currentArray,
250
+ state,
251
+ settings,
252
+ isStateful
253
+ } = params;
254
+
255
+ if (!settings.allowMultiple) {
256
+ return currentArray?.[0] ?? null;
257
+ }
258
+
259
+ if (!currentArray) {
260
+ return null;
261
+ }
262
+
263
+ if (isStateful) {
264
+ return state
265
+ ? settings.allowMultiple.matcher(state, currentArray)
266
+ : null
267
+ }
268
+
269
+ return settings.allowMultiple.matcher(desired!, currentArray);
270
+ }
271
+
278
272
  /**
279
273
  * Only keep relevant params for the plan. We don't want to change settings that were not already
280
274
  * defined.
@@ -284,18 +278,18 @@ export class Plan<T extends StringIndexedObject> {
284
278
  * or wants to set. If a parameter is not specified then it's not managed by Codify.
285
279
  */
286
280
  private static filterCurrentParams<T extends StringIndexedObject>(params: {
287
- desiredParameters: Partial<T> | null,
288
- currentParameters: Partial<T> | null,
289
- stateParameters: Partial<T> | null,
281
+ desired: Partial<T> | null,
282
+ current: Partial<T> | null,
283
+ state: Partial<T> | null,
290
284
  settings: ResourceSettings<T>,
291
- statefulMode: boolean,
285
+ isStateful: boolean,
292
286
  }): Partial<T> | null {
293
287
  const {
294
- desiredParameters: desired,
295
- currentParameters: current,
296
- stateParameters: state,
288
+ desired,
289
+ current,
290
+ state,
297
291
  settings,
298
- statefulMode
292
+ isStateful
299
293
  } = params;
300
294
 
301
295
  if (!current) {
@@ -309,7 +303,7 @@ export class Plan<T extends StringIndexedObject> {
309
303
 
310
304
  // For stateful mode, we're done after filtering by the keys of desired + state. Stateless mode
311
305
  // requires additional filtering for stateful parameter arrays and objects.
312
- if (statefulMode) {
306
+ if (isStateful) {
313
307
  return filteredCurrent;
314
308
  }
315
309
 
@@ -327,7 +321,7 @@ export class Plan<T extends StringIndexedObject> {
327
321
  return null;
328
322
  }
329
323
 
330
- if (statefulMode) {
324
+ if (isStateful) {
331
325
  const keys = new Set([...Object.keys(state ?? {}), ...Object.keys(desired ?? {})]);
332
326
  return Object.fromEntries(
333
327
  Object.entries(current)
@@ -426,7 +420,7 @@ export class Plan<T extends StringIndexedObject> {
426
420
  return {
427
421
  planId: this.id,
428
422
  operation: this.changeSet.operation,
429
- statefulMode: this.statefulMode,
423
+ isStateful: this.isStateful,
430
424
  resourceName: this.coreParameters.name,
431
425
  resourceType: this.coreParameters.type,
432
426
  parameters: this.changeSet.parameterChanges,
@@ -56,7 +56,7 @@ describe('Plugin tests', () => {
56
56
  parameters: [
57
57
  { name: 'propA', operation: ParameterOperation.ADD, newValue: 'abc', previousValue: null },
58
58
  ],
59
- statefulMode: false,
59
+ isStateful: false,
60
60
  };
61
61
 
62
62
  await plugin.apply({ plan });
@@ -77,7 +77,7 @@ describe('Plugin tests', () => {
77
77
  parameters: [
78
78
  { name: 'propA', operation: ParameterOperation.REMOVE, newValue: null, previousValue: 'abc' },
79
79
  ],
80
- statefulMode: true,
80
+ isStateful: true,
81
81
  };
82
82
 
83
83
  await testPlugin.apply({ plan })
@@ -100,7 +100,7 @@ describe('Plugin tests', () => {
100
100
  parameters: [
101
101
  { name: 'propA', operation: ParameterOperation.MODIFY, newValue: 'def', previousValue: 'abc' },
102
102
  ],
103
- statefulMode: false,
103
+ isStateful: false,
104
104
  };
105
105
 
106
106
  await testPlugin.apply({ plan })
@@ -124,7 +124,7 @@ describe('Plugin tests', () => {
124
124
  parameters: [
125
125
  { name: 'propA', operation: ParameterOperation.MODIFY, newValue: 'def', previousValue: 'abc' },
126
126
  ],
127
- statefulMode: false,
127
+ isStateful: false,
128
128
  };
129
129
 
130
130
  await testPlugin.apply({ plan })
@@ -224,7 +224,7 @@ describe('Plugin tests', () => {
224
224
  parameters: [
225
225
  { name: 'propA', operation: ParameterOperation.MODIFY, newValue: 'def', previousValue: 'abc' },
226
226
  ],
227
- statefulMode: false,
227
+ isStateful: false,
228
228
  };
229
229
 
230
230
  await expect(() => testPlugin.apply({ plan }))
@@ -245,9 +245,8 @@ describe('Plugin tests', () => {
245
245
 
246
246
  const testPlugin = Plugin.create('testPlugin', [resource as any]);
247
247
  await testPlugin.plan({
248
- desired: {
249
- type: 'testResource'
250
- },
248
+ core: { type: 'testResource' },
249
+ desired: {},
251
250
  state: undefined,
252
251
  isStateful: false,
253
252
  })
@@ -275,7 +274,7 @@ describe('Plugin tests', () => {
275
274
  parameters: [
276
275
  { name: 'propA', operation: ParameterOperation.ADD, newValue: 'abc', previousValue: null },
277
276
  ],
278
- statefulMode: false,
277
+ isStateful: false,
279
278
  };
280
279
 
281
280
  await testPlugin.apply({ plan })
@@ -9,6 +9,7 @@ import {
9
9
  PlanRequestData,
10
10
  PlanResponseData,
11
11
  ResourceConfig,
12
+ ResourceJson,
12
13
  ValidateRequestData,
13
14
  ValidateResponseData
14
15
  } from 'codify-schemas';
@@ -83,18 +84,20 @@ export class Plugin {
83
84
  }
84
85
 
85
86
  async import(data: ImportRequestData): Promise<ImportResponseData> {
86
- if (!this.resourceControllers.has(data.config.type)) {
87
- throw new Error(`Cannot get info for resource ${data.config.type}, resource doesn't exist`);
87
+ const { core, parameters } = data;
88
+
89
+ if (!this.resourceControllers.has(core.type)) {
90
+ throw new Error(`Cannot get info for resource ${core.type}, resource doesn't exist`);
88
91
  }
89
92
 
90
93
  const result = await ptyLocalStorage.run(this.planPty, () =>
91
94
  this.resourceControllers
92
- .get(data.config.type!)
93
- ?.import(data.config)
95
+ .get(core.type!)
96
+ ?.import(core, parameters)
94
97
  )
95
98
 
96
99
  return {
97
- request: data.config,
100
+ request: data,
98
101
  result: result ?? [],
99
102
  }
100
103
  }
@@ -102,13 +105,15 @@ export class Plugin {
102
105
  async validate(data: ValidateRequestData): Promise<ValidateResponseData> {
103
106
  const validationResults = [];
104
107
  for (const config of data.configs) {
105
- if (!this.resourceControllers.has(config.type)) {
106
- throw new Error(`Resource type not found: ${config.type}`);
108
+ const { core, parameters } = config;
109
+
110
+ if (!this.resourceControllers.has(core.type)) {
111
+ throw new Error(`Resource type not found: ${core.type}`);
107
112
  }
108
113
 
109
114
  const validation = await this.resourceControllers
110
- .get(config.type)!
111
- .validate(config);
115
+ .get(core.type)!
116
+ .validate(core, parameters);
112
117
 
113
118
  validationResults.push(validation);
114
119
  }
@@ -120,16 +125,17 @@ export class Plugin {
120
125
  }
121
126
 
122
127
  async plan(data: PlanRequestData): Promise<PlanResponseData> {
123
- const type = data.desired?.type ?? data.state?.type
128
+ const { type } = data.core
124
129
 
125
- if (!type || !this.resourceControllers.has(type)) {
130
+ if (!this.resourceControllers.has(type)) {
126
131
  throw new Error(`Resource type not found: ${type}`);
127
132
  }
128
133
 
129
134
  const plan = await ptyLocalStorage.run(this.planPty, async () => this.resourceControllers.get(type)!.plan(
130
- data.desired ?? null,
131
- data.state ?? null,
132
- data.isStateful
135
+ data.core,
136
+ data.desired ?? null,
137
+ data.state ?? null,
138
+ data.isStateful
133
139
  ))
134
140
 
135
141
  this.planStorage.set(plan.id, plan);
@@ -155,9 +161,10 @@ export class Plugin {
155
161
  // Default back desired back to current if it is not defined (for destroys only)
156
162
  const validationPlan = await ptyLocalStorage.run(new BackgroundPty(), async () => {
157
163
  const result = await resource.plan(
164
+ plan.coreParameters,
158
165
  plan.desiredConfig,
159
166
  plan.desiredConfig ?? plan.currentConfig,
160
- plan.statefulMode
167
+ plan.isStateful
161
168
  );
162
169
 
163
170
  await getPty().kill();
@@ -192,5 +199,6 @@ export class Plugin {
192
199
  return Plan.fromResponse(planRequest, resource.parsedSettings.defaultValues);
193
200
  }
194
201
 
195
- protected async crossValidateResources(configs: ResourceConfig[]): Promise<void> {}
202
+ protected async crossValidateResources(resources: ResourceJson[]): Promise<void> {
203
+ }
196
204
  }