codify-plugin-lib 1.0.75 → 1.0.77

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.
Files changed (76) hide show
  1. package/.eslintrc.json +11 -4
  2. package/.github/workflows/release.yaml +19 -0
  3. package/.github/workflows/unit-test-ci.yaml +19 -0
  4. package/dist/entities/plugin.d.ts +1 -1
  5. package/dist/entities/plugin.js +5 -5
  6. package/dist/entities/resource-options.d.ts +6 -6
  7. package/dist/entities/resource-options.js +7 -9
  8. package/dist/entities/resource.d.ts +2 -3
  9. package/dist/entities/resource.js +2 -2
  10. package/dist/errors.d.ts +4 -0
  11. package/dist/errors.js +7 -0
  12. package/dist/index.d.ts +10 -10
  13. package/dist/index.js +9 -9
  14. package/dist/messages/handlers.d.ts +1 -1
  15. package/dist/messages/handlers.js +25 -24
  16. package/dist/plan/change-set.d.ts +37 -0
  17. package/dist/plan/change-set.js +146 -0
  18. package/dist/plan/plan-types.d.ts +23 -0
  19. package/dist/plan/plan-types.js +1 -0
  20. package/dist/plan/plan.d.ts +59 -0
  21. package/dist/plan/plan.js +228 -0
  22. package/dist/plugin/plugin.d.ts +17 -0
  23. package/dist/plugin/plugin.js +83 -0
  24. package/dist/resource/config-parser.d.ts +14 -0
  25. package/dist/resource/config-parser.js +48 -0
  26. package/dist/resource/parsed-resource-settings.d.ts +26 -0
  27. package/dist/resource/parsed-resource-settings.js +126 -0
  28. package/dist/resource/resource-controller.d.ts +30 -0
  29. package/dist/resource/resource-controller.js +247 -0
  30. package/dist/resource/resource-settings.d.ts +149 -0
  31. package/dist/resource/resource-settings.js +9 -0
  32. package/dist/resource/resource.d.ts +137 -0
  33. package/dist/resource/resource.js +44 -0
  34. package/dist/resource/stateful-parameter.d.ts +164 -0
  35. package/dist/resource/stateful-parameter.js +94 -0
  36. package/dist/utils/utils.d.ts +19 -3
  37. package/dist/utils/utils.js +52 -3
  38. package/package.json +6 -4
  39. package/src/index.ts +10 -11
  40. package/src/messages/handlers.test.ts +21 -42
  41. package/src/messages/handlers.ts +28 -27
  42. package/src/plan/change-set.test.ts +220 -0
  43. package/src/plan/change-set.ts +225 -0
  44. package/src/plan/plan-types.ts +27 -0
  45. package/src/{entities → plan}/plan.test.ts +35 -29
  46. package/src/plan/plan.ts +353 -0
  47. package/src/{entities → plugin}/plugin.test.ts +14 -13
  48. package/src/{entities → plugin}/plugin.ts +32 -27
  49. package/src/resource/config-parser.ts +77 -0
  50. package/src/{entities/resource-options.test.ts → resource/parsed-resource-settings.test.ts} +8 -7
  51. package/src/resource/parsed-resource-settings.ts +179 -0
  52. package/src/{entities/resource-stateful-mode.test.ts → resource/resource-controller-stateful-mode.test.ts} +36 -39
  53. package/src/{entities/resource.test.ts → resource/resource-controller.test.ts} +116 -176
  54. package/src/resource/resource-controller.ts +340 -0
  55. package/src/resource/resource-settings.test.ts +494 -0
  56. package/src/resource/resource-settings.ts +192 -0
  57. package/src/resource/resource.ts +149 -0
  58. package/src/resource/stateful-parameter.test.ts +93 -0
  59. package/src/resource/stateful-parameter.ts +217 -0
  60. package/src/utils/test-utils.test.ts +87 -0
  61. package/src/utils/utils.test.ts +2 -2
  62. package/src/utils/utils.ts +51 -5
  63. package/tsconfig.json +0 -1
  64. package/vitest.config.ts +10 -0
  65. package/src/entities/change-set.test.ts +0 -155
  66. package/src/entities/change-set.ts +0 -244
  67. package/src/entities/plan-types.ts +0 -44
  68. package/src/entities/plan.ts +0 -178
  69. package/src/entities/resource-options.ts +0 -156
  70. package/src/entities/resource-parameters.test.ts +0 -604
  71. package/src/entities/resource-types.ts +0 -31
  72. package/src/entities/resource.ts +0 -471
  73. package/src/entities/stateful-parameter.test.ts +0 -114
  74. package/src/entities/stateful-parameter.ts +0 -92
  75. package/src/entities/transform-parameter.ts +0 -13
  76. /package/src/{entities/errors.ts → errors.ts} +0 -0
@@ -0,0 +1,247 @@
1
+ import { Ajv } from 'ajv';
2
+ import { ParameterOperation, ResourceOperation } from 'codify-schemas';
3
+ import { Plan } from '../plan/plan.js';
4
+ import { ConfigParser } from './config-parser.js';
5
+ import { ParsedResourceSettings } from './parsed-resource-settings.js';
6
+ export class ResourceController {
7
+ resource;
8
+ settings;
9
+ parsedSettings;
10
+ typeId;
11
+ dependencies;
12
+ ajv;
13
+ schemaValidator;
14
+ constructor(resource) {
15
+ this.resource = resource;
16
+ this.settings = resource.getSettings();
17
+ this.typeId = this.settings.id;
18
+ this.dependencies = this.settings.dependencies ?? [];
19
+ if (this.settings.schema) {
20
+ this.ajv = new Ajv({
21
+ allErrors: true,
22
+ strict: true,
23
+ strictRequired: false,
24
+ });
25
+ this.schemaValidator = this.ajv.compile(this.settings.schema);
26
+ }
27
+ this.parsedSettings = new ParsedResourceSettings(this.settings);
28
+ }
29
+ async initialize() {
30
+ return this.resource.initialize();
31
+ }
32
+ async validate(parameters, resourceMetaData) {
33
+ if (this.schemaValidator) {
34
+ const isValid = this.schemaValidator(parameters);
35
+ if (!isValid) {
36
+ return {
37
+ isValid: false,
38
+ resourceName: resourceMetaData.name,
39
+ resourceType: resourceMetaData.type,
40
+ schemaValidationErrors: this.schemaValidator?.errors ?? [],
41
+ };
42
+ }
43
+ }
44
+ let isValid = true;
45
+ let customValidationErrorMessage;
46
+ try {
47
+ await this.resource.validate(parameters);
48
+ }
49
+ catch (error) {
50
+ isValid = false;
51
+ customValidationErrorMessage = error.message;
52
+ }
53
+ if (!isValid) {
54
+ return {
55
+ customValidationErrorMessage,
56
+ isValid: false,
57
+ resourceName: resourceMetaData.name,
58
+ resourceType: resourceMetaData.type,
59
+ schemaValidationErrors: this.schemaValidator?.errors ?? [],
60
+ };
61
+ }
62
+ return {
63
+ isValid: true,
64
+ resourceName: resourceMetaData.name,
65
+ resourceType: resourceMetaData.type,
66
+ schemaValidationErrors: [],
67
+ };
68
+ }
69
+ async plan(desiredConfig, stateConfig = null, statefulMode = false) {
70
+ this.validatePlanInputs(desiredConfig, stateConfig, statefulMode);
71
+ this.addDefaultValues(desiredConfig);
72
+ await this.applyTransformParameters(desiredConfig);
73
+ // Parse data from the user supplied config
74
+ const parsedConfig = new ConfigParser(desiredConfig, stateConfig, this.parsedSettings.statefulParameters);
75
+ const { coreParameters, desiredParameters, stateParameters, allNonStatefulParameters, allStatefulParameters, } = parsedConfig;
76
+ // Refresh resource parameters. This refreshes the parameters that configure the resource itself
77
+ const currentParametersArray = await this.refreshNonStatefulParameters(allNonStatefulParameters);
78
+ // Short circuit here. If the resource is non-existent, there's no point checking stateful parameters
79
+ if (currentParametersArray === null
80
+ || currentParametersArray === undefined
81
+ || this.settings.allowMultiple // Stateful parameters are not supported currently if allowMultiple is true
82
+ || currentParametersArray.length === 0
83
+ || currentParametersArray.filter(Boolean).length === 0) {
84
+ return Plan.calculate({
85
+ desiredParameters,
86
+ currentParametersArray,
87
+ stateParameters,
88
+ coreParameters,
89
+ settings: this.parsedSettings,
90
+ statefulMode,
91
+ });
92
+ }
93
+ // Refresh stateful parameters. These parameters have state external to the resource. allowMultiple
94
+ // does not work together with stateful parameters
95
+ const statefulCurrentParameters = await this.refreshStatefulParameters(allStatefulParameters);
96
+ return Plan.calculate({
97
+ desiredParameters,
98
+ currentParametersArray: [{ ...currentParametersArray[0], ...statefulCurrentParameters }],
99
+ stateParameters,
100
+ coreParameters,
101
+ settings: this.parsedSettings,
102
+ statefulMode
103
+ });
104
+ }
105
+ async apply(plan) {
106
+ if (plan.getResourceType() !== this.typeId) {
107
+ throw new Error(`Internal error: Plan set to wrong resource during apply. Expected ${this.typeId} but got: ${plan.getResourceType()}`);
108
+ }
109
+ switch (plan.changeSet.operation) {
110
+ case ResourceOperation.CREATE: {
111
+ return this.applyCreate(plan);
112
+ }
113
+ case ResourceOperation.MODIFY: {
114
+ return this.applyModify(plan);
115
+ }
116
+ case ResourceOperation.RECREATE: {
117
+ await this.applyDestroy(plan);
118
+ return this.applyCreate(plan);
119
+ }
120
+ case ResourceOperation.DESTROY: {
121
+ return this.applyDestroy(plan);
122
+ }
123
+ }
124
+ }
125
+ async applyCreate(plan) {
126
+ await this.resource.create(plan);
127
+ const statefulParameterChanges = this.getSortedStatefulParameterChanges(plan.changeSet.parameterChanges);
128
+ for (const parameterChange of statefulParameterChanges) {
129
+ const statefulParameter = this.parsedSettings.statefulParameters.get(parameterChange.name);
130
+ await statefulParameter.add(parameterChange.newValue, plan);
131
+ }
132
+ }
133
+ async applyModify(plan) {
134
+ const parameterChanges = plan
135
+ .changeSet
136
+ .parameterChanges
137
+ .filter((c) => c.operation !== ParameterOperation.NOOP);
138
+ const statelessParameterChanges = parameterChanges
139
+ .filter((pc) => !this.parsedSettings.statefulParameters.has(pc.name));
140
+ for (const pc of statelessParameterChanges) {
141
+ await this.resource.modify(pc, plan);
142
+ }
143
+ const statefulParameterChanges = this.getSortedStatefulParameterChanges(plan.changeSet.parameterChanges);
144
+ for (const parameterChange of statefulParameterChanges) {
145
+ const statefulParameter = this.parsedSettings.statefulParameters.get(parameterChange.name);
146
+ switch (parameterChange.operation) {
147
+ case ParameterOperation.ADD: {
148
+ await statefulParameter.add(parameterChange.newValue, plan);
149
+ break;
150
+ }
151
+ case ParameterOperation.MODIFY: {
152
+ await statefulParameter.modify(parameterChange.newValue, parameterChange.previousValue, plan);
153
+ break;
154
+ }
155
+ case ParameterOperation.REMOVE: {
156
+ await statefulParameter.remove(parameterChange.previousValue, plan);
157
+ break;
158
+ }
159
+ }
160
+ }
161
+ }
162
+ async applyDestroy(plan) {
163
+ // If this option is set (defaults to false), then stateful parameters need to be destroyed
164
+ // as well. This means that the stateful parameter wouldn't have been normally destroyed with applyDestroy()
165
+ if (this.settings.removeStatefulParametersBeforeDestroy) {
166
+ const statefulParameterChanges = this.getSortedStatefulParameterChanges(plan.changeSet.parameterChanges);
167
+ for (const parameterChange of statefulParameterChanges) {
168
+ const statefulParameter = this.parsedSettings.statefulParameters.get(parameterChange.name);
169
+ await statefulParameter.remove(parameterChange.previousValue, plan);
170
+ }
171
+ }
172
+ await this.resource.destroy(plan);
173
+ }
174
+ validateRefreshResults(refresh) {
175
+ if (!refresh) {
176
+ return;
177
+ }
178
+ if (!this.settings.allowMultiple && refresh.length > 1) {
179
+ throw new Error(`Resource: ${this.settings.id}. Allow multiple was set to false but multiple refresh results were returned.
180
+
181
+ ${JSON.stringify(refresh, null, 2)}
182
+ `);
183
+ }
184
+ }
185
+ async applyTransformParameters(desired) {
186
+ if (!desired) {
187
+ return;
188
+ }
189
+ for (const [key, inputTransformation] of Object.entries(this.parsedSettings.inputTransformations)) {
190
+ if (desired[key] === undefined || !inputTransformation) {
191
+ continue;
192
+ }
193
+ desired[key] = await inputTransformation(desired[key]);
194
+ }
195
+ if (this.settings.inputTransformation) {
196
+ const transformed = await this.settings.inputTransformation(desired);
197
+ Object.keys(desired).forEach((k) => delete desired[k]);
198
+ Object.assign(desired, transformed);
199
+ }
200
+ }
201
+ addDefaultValues(desired) {
202
+ if (!desired) {
203
+ return;
204
+ }
205
+ for (const [key, defaultValue] of Object.entries(this.parsedSettings.defaultValues)) {
206
+ if (defaultValue !== undefined && (desired[key] === undefined || desired[key] === null)) {
207
+ desired[key] = defaultValue;
208
+ }
209
+ }
210
+ }
211
+ async refreshNonStatefulParameters(resourceParameters) {
212
+ const result = await this.resource.refresh(resourceParameters);
213
+ const currentParametersArray = Array.isArray(result) || result === null
214
+ ? result
215
+ : [result];
216
+ this.validateRefreshResults(currentParametersArray);
217
+ return currentParametersArray;
218
+ }
219
+ // Refresh stateful parameters
220
+ // This refreshes parameters that are stateful (they can be added, deleted separately from the resource)
221
+ async refreshStatefulParameters(statefulParametersConfig) {
222
+ const result = {};
223
+ const sortedEntries = Object.entries(statefulParametersConfig)
224
+ .sort(([key1], [key2]) => this.parsedSettings.statefulParameterOrder.get(key1) - this.parsedSettings.statefulParameterOrder.get(key2));
225
+ for (const [key, desiredValue] of sortedEntries) {
226
+ const statefulParameter = this.parsedSettings.statefulParameters.get(key);
227
+ if (!statefulParameter) {
228
+ throw new Error(`Stateful parameter ${key} was not found`);
229
+ }
230
+ result[key] = await statefulParameter.refresh(desiredValue ?? null);
231
+ }
232
+ return result;
233
+ }
234
+ validatePlanInputs(desired, current, statefulMode) {
235
+ if (!desired && !current) {
236
+ throw new Error('Desired config and current config cannot both be missing');
237
+ }
238
+ if (!statefulMode && !desired) {
239
+ throw new Error('Desired config must be provided in non-stateful mode');
240
+ }
241
+ }
242
+ getSortedStatefulParameterChanges(parameterChanges) {
243
+ return parameterChanges
244
+ .filter((pc) => this.parsedSettings.statefulParameters.has(pc.name))
245
+ .sort((a, b) => this.parsedSettings.statefulParameterOrder.get(a.name) - this.parsedSettings.statefulParameterOrder.get(b.name));
246
+ }
247
+ }
@@ -0,0 +1,149 @@
1
+ import { StringIndexedObject } from 'codify-schemas';
2
+ import { StatefulParameter } from './stateful-parameter.js';
3
+ /**
4
+ * The configuration and settings for a resource.
5
+ */
6
+ export interface ResourceSettings<T extends StringIndexedObject> {
7
+ /**
8
+ * The typeId of the resource.
9
+ */
10
+ id: string;
11
+ /**
12
+ * Schema to validate user configs with. Must be in the format JSON Schema draft07
13
+ */
14
+ schema?: unknown;
15
+ /**
16
+ * Allow multiple of the same resource to unique. Set truthy if
17
+ * multiples are allowed, for example for applications, there can be multiple copy of the same application installed
18
+ * on the system. Or there can be multiple git repos. Defaults to false.
19
+ */
20
+ allowMultiple?: {
21
+ /**
22
+ * If multiple copies are allowed then a matcher must be defined to match the desired
23
+ * config with one of the resources currently existing on the system. Return null if there is no match.
24
+ *
25
+ * @param current An array of resources found installed on the system
26
+ * @param desired The desired config to match.
27
+ *
28
+ * @return The matched resource.
29
+ */
30
+ matcher: (desired: Partial<T>, current: Partial<T>[]) => Partial<T>;
31
+ };
32
+ /**
33
+ * If true, {@link StatefulParameter} remove() will be called before resource destruction. This is useful
34
+ * if the stateful parameter needs to be first uninstalled (cleanup) before the overall resource can be
35
+ * uninstalled. Defaults to false.
36
+ */
37
+ removeStatefulParametersBeforeDestroy?: boolean;
38
+ /**
39
+ * An array of type ids of resources that this resource depends on. This affects the order in which multiple resources are
40
+ * planned and applied.
41
+ */
42
+ dependencies?: string[];
43
+ /**
44
+ * Options for configuring parameters operations including overriding the equals function, adding default values
45
+ * and applying any input transformations. Use parameter settings to define stateful parameters as well.
46
+ */
47
+ parameterSettings?: Partial<Record<keyof T, ParameterSetting>>;
48
+ /**
49
+ * A config level transformation that is only applied to the user supplied desired config. This transformation is allowed
50
+ * to add, remove or modify keys as well as values. Changing this transformation for existing libraries will mess up existing states.
51
+ *
52
+ * @param desired
53
+ */
54
+ inputTransformation?: (desired: Partial<T>) => Promise<unknown> | unknown;
55
+ }
56
+ /**
57
+ * The type of parameter. This value is mainly used to determine a pre-set equality method for comparing the current
58
+ * config with desired config. Certain types will have additional options to help support it. For example the type
59
+ * stateful requires a stateful parameter definition and type array takes an isElementEqual method.
60
+ */
61
+ export type ParameterSettingType = 'any' | 'array' | 'boolean' | 'directory' | 'number' | 'stateful' | 'string' | 'version';
62
+ /**
63
+ * Typing information for the parameter setting. This represents a setting on a specific parameter within a
64
+ * resource. Options for configuring parameters operations including overriding the equals function, adding default values
65
+ * and applying any input transformations. See {@link DefaultParameterSetting } for more information.
66
+ * Use parameter settings to define stateful parameters as well.
67
+ */
68
+ export type ParameterSetting = ArrayParameterSetting | DefaultParameterSetting | StatefulParameterSetting;
69
+ /**
70
+ * The parent class for parameter settings. The options are applicable to array parameter settings
71
+ * as well.
72
+ */
73
+ export interface DefaultParameterSetting {
74
+ /**
75
+ * The type of the value of this parameter. See {@link ParameterSettingType} for the available options. This value
76
+ * is mainly used to determine the equality method when performing diffing.
77
+ */
78
+ type?: ParameterSettingType;
79
+ /**
80
+ * Default value for the parameter. If a value is not provided in the config, then this value will be used.
81
+ */
82
+ default?: unknown;
83
+ /**
84
+ * A transformation of the input value for this parameter. This transformation is only applied to the desired parameter
85
+ * value supplied by the user.
86
+ *
87
+ * @param input The original parameter value from the desired config.
88
+ */
89
+ inputTransformation?: (input: any) => Promise<any> | unknown;
90
+ /**
91
+ * Customize the equality comparison for a parameter. This is used in the diffing algorithm for generating the plan.
92
+ * This value will override the pre-set equality function from the type. Return true if the desired value is
93
+ * equivalent to the current value.
94
+ *
95
+ * @param desired The desired value.
96
+ * @param current The current value.
97
+ *
98
+ * @return Return true if equal
99
+ */
100
+ isEqual?: (desired: any, current: any) => boolean;
101
+ /**
102
+ * Chose if the resource can be modified instead of re-created when there is a change to this parameter.
103
+ * Defaults to false (re-create).
104
+ *
105
+ * Examples:
106
+ * 1. Settings like git user name and git user email that have setter calls and don't require the re-installation of git
107
+ * 2. AWS profile secret keys that can be updated without the re-installation of AWS CLI
108
+ */
109
+ canModify?: boolean;
110
+ }
111
+ /**
112
+ * Array type specific settings. See {@link DefaultParameterSetting } for a full list of options.
113
+ */
114
+ export interface ArrayParameterSetting extends DefaultParameterSetting {
115
+ type: 'array';
116
+ /**
117
+ * An element level equality function for arrays. The diffing algorithm will take isElementEqual and use it in a
118
+ * O(n^2) equality comparison to determine if the overall array is equal. This value will override the pre-set equality
119
+ * function for arrays (desired === current). Return true if the desired element is equivalent to the current element.
120
+ *
121
+ * @param desired An element of the desired array
122
+ * @param current An element of the current array
123
+ *
124
+ * @return Return true if desired is equivalent to current.
125
+ */
126
+ isElementEqual?: (desired: any, current: any) => boolean;
127
+ }
128
+ /**
129
+ * Stateful parameter type specific settings. A stateful parameter is a sub-resource that can hold its own
130
+ * state but is still tied to the overall state of the resource. For example 'homebrew' is represented
131
+ * as a resource and taps, formulas and casks are represented as a stateful parameter. A formula can be installed,
132
+ * modified and removed (has state) but it is still tied to the overall lifecycle of homebrew.
133
+ *
134
+ */
135
+ export interface StatefulParameterSetting extends DefaultParameterSetting {
136
+ type: 'stateful';
137
+ /**
138
+ * The stateful parameter definition. A stateful parameter is a sub-resource that can hold its own
139
+ * state but is still tied to the overall state of the resource. For example 'homebrew' is represented
140
+ * as a resource and taps, formulas and casks are represented as a stateful parameter. A formula can be installed,
141
+ * modified and removed (has state) but it is still tied to the overall lifecycle of homebrew.
142
+ */
143
+ definition: StatefulParameter<any, unknown>;
144
+ /**
145
+ * The order multiple stateful parameters should be applied in. The order is applied in ascending order (1, 2, 3...).
146
+ */
147
+ order?: number;
148
+ }
149
+ export declare const ParameterEqualsDefaults: Partial<Record<ParameterSettingType, (a: unknown, b: unknown) => boolean>>;
@@ -0,0 +1,9 @@
1
+ import path from 'node:path';
2
+ import { untildify } from '../utils/utils.js';
3
+ export const ParameterEqualsDefaults = {
4
+ 'boolean': (a, b) => Boolean(a) === Boolean(b),
5
+ 'directory': (a, b) => path.resolve(untildify(String(a))) === path.resolve(untildify(String(b))),
6
+ 'number': (a, b) => Number(a) === Number(b),
7
+ 'string': (a, b) => String(a) === String(b),
8
+ 'version': (desired, current) => String(current).includes(String(desired))
9
+ };
@@ -0,0 +1,137 @@
1
+ import { StringIndexedObject } from 'codify-schemas';
2
+ import { ParameterChange } from '../plan/change-set.js';
3
+ import { CreatePlan, DestroyPlan, ModifyPlan } from '../plan/plan-types.js';
4
+ import { ResourceSettings } from './resource-settings.js';
5
+ /**
6
+ * A resource represents an object on the system (application, CLI tool, or setting)
7
+ * that has state and can be created and destroyed. Examples of resources include CLI tools
8
+ * like homebrew, docker, and xcode-tools; applications like Google Chrome, Zoom, and OpenVPN;
9
+ * and settings like AWS profiles, git configs and system preference settings.
10
+ */
11
+ export declare abstract class Resource<T extends StringIndexedObject> {
12
+ /**
13
+ * Return the settings for the resource. Consult the typing for {@link ResourceSettings} for
14
+ * a description of the options.
15
+ *
16
+ * **Parameters**:
17
+ * - id: The id of the resource. This translates to the `type` id parameter in codify.json configs
18
+ * - schema: A JSON schema used to validate user input
19
+ * - allowMultiple: Allow multiple copies of the resource to exist at the same time. If true then,
20
+ * a matcher must be defined that matches a user defined config and a single resource on the system.
21
+ * - removeStatefulParametersBeforeDestory: Call the delete methods of stateful parameters before destorying
22
+ * the base resource. Defaults to false.
23
+ * - dependencies: Specify the ids of any resources that this resource depends on
24
+ * - parameterSettings: Parameter specific settings. Use this to define custom equals functions, default values
25
+ * and input transformations
26
+ * - inputTransformation: Transform the input value.
27
+ *
28
+ * @return ResourceSettings The resource settings
29
+ */
30
+ abstract getSettings(): ResourceSettings<T>;
31
+ initialize(): Promise<void>;
32
+ /**
33
+ * Add custom validation logic in-addition to the default schema validation.
34
+ * In this method throw an error if the object did not validate. The message of the
35
+ * error will be shown to the user.
36
+ * @param parameters
37
+ */
38
+ validate(parameters: Partial<T>): Promise<void>;
39
+ /**
40
+ * Return the status of the resource on the system. If multiple resources exist, then return all instances of
41
+ * the resource back. Query for the individual parameters specified in the parameter param.
42
+ * Return null if the resource does not exist.
43
+ *
44
+ * Example (Android Studios Resource):
45
+ * 1. Receive Input:
46
+ * ```
47
+ * {
48
+ * name: 'Android Studios.app'
49
+ * directory: '/Application',
50
+ * version: '2023.2'
51
+ * }
52
+ * ```
53
+ * 2. Query the system for any installed Android studio versions.
54
+ * 3. In this example we find that there is an 2023.2 version installed and an
55
+ * additional 2024.3-beta version installed as well.
56
+ * 4. We would return:
57
+ * ```
58
+ * [
59
+ * { name: 'Android Studios.app', directory: '/Application', version: '2023.2' },
60
+ * { name: 'Android Studios Preview.app', directory: '/Application', version: '2024.3' },
61
+ * ]
62
+ * ```
63
+ *
64
+ * @param parameters The parameters to refresh. In stateless mode this will be the parameters
65
+ * of the desired config. In stateful mode, this will be parameters of the state config + the desired
66
+ * config of any new parameters.
67
+ *
68
+ * @return A config or an array of configs representing the status of the resource on the
69
+ * system currently
70
+ */
71
+ abstract refresh(parameters: Partial<T>): Promise<Array<Partial<T>> | Partial<T> | null>;
72
+ /**
73
+ * Create the resource (install) based on the parameters passed in. Only the desired parameters will
74
+ * be non-null because in a CREATE plan, the current value is null.
75
+ *
76
+ * Example (Android Studios Resource):
77
+ * 1. We receive a plan of:
78
+ * ```
79
+ * Plan {
80
+ * desiredConfig: {
81
+ * name: 'Android Studios.app',
82
+ * directory: '/Application',
83
+ * version: '2023.2'
84
+ * }
85
+ * currentConfig: null,
86
+ * }
87
+ * ```
88
+ * 2. Install version Android Studios 2023.2 and then return.
89
+ *
90
+ * @param plan The plan of what to install. Use only the desiredConfig because currentConfig is null.
91
+ */
92
+ abstract create(plan: CreatePlan<T>): Promise<void>;
93
+ /**
94
+ * Modify a single parameter of a resource. Modify is optional to override and is only called
95
+ * when a resourceSetting was set to `canModify = true`. This method should only modify
96
+ * a single parameter at a time as specified by the first parameter: ParameterChange.
97
+ *
98
+ * Example (AWS Profile Resource):
99
+ * 1. We receive a parameter change of:
100
+ * ```
101
+ * {
102
+ * name: 'awsAccessKeyId',
103
+ * operation: ParameterOperation.MODIFY,
104
+ * newValue: '123456',
105
+ * previousValue: 'abcdef'
106
+ * }
107
+ * ```
108
+ * 2. Use an if statement to only apply this operation for the parameter `awsAccessKeyId`
109
+ * 3. Update the value of the `aws_access_key_id` to the `newValue` specified in the parameter change
110
+ *
111
+ * @param pc ParameterChange, the parameter name and values to modify on the resource
112
+ * @param plan The overall plan that triggered the modify operation
113
+ */
114
+ modify(pc: ParameterChange<T>, plan: ModifyPlan<T>): Promise<void>;
115
+ /**
116
+ * Destroy the resource (uninstall) based on the parameters passed in. Only the current parameters will
117
+ * be non-null because in a DESTROY plan, the desired value is null. This method will only be called in
118
+ * stateful mode.
119
+ *
120
+ * Example (Android Studios Resource):
121
+ * 1. We receive a plan of:
122
+ * ```
123
+ * Plan {
124
+ * currentConfig: {
125
+ * name: 'Android Studios.app',
126
+ * directory: '/Application',
127
+ * version: '2022.4'
128
+ * },
129
+ * desiredConfig: null
130
+ * }
131
+ * ```
132
+ * 2. Uninstall version Android Studios 2022.4 and then return.
133
+ *
134
+ * @param plan The plan of what to uninstall. Use only the currentConfig because desiredConfig is null.
135
+ */
136
+ abstract destroy(plan: DestroyPlan<T>): Promise<void>;
137
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A resource represents an object on the system (application, CLI tool, or setting)
3
+ * that has state and can be created and destroyed. Examples of resources include CLI tools
4
+ * like homebrew, docker, and xcode-tools; applications like Google Chrome, Zoom, and OpenVPN;
5
+ * and settings like AWS profiles, git configs and system preference settings.
6
+ */
7
+ export class Resource {
8
+ async initialize() {
9
+ }
10
+ ;
11
+ /**
12
+ * Add custom validation logic in-addition to the default schema validation.
13
+ * In this method throw an error if the object did not validate. The message of the
14
+ * error will be shown to the user.
15
+ * @param parameters
16
+ */
17
+ async validate(parameters) {
18
+ }
19
+ ;
20
+ /**
21
+ * Modify a single parameter of a resource. Modify is optional to override and is only called
22
+ * when a resourceSetting was set to `canModify = true`. This method should only modify
23
+ * a single parameter at a time as specified by the first parameter: ParameterChange.
24
+ *
25
+ * Example (AWS Profile Resource):
26
+ * 1. We receive a parameter change of:
27
+ * ```
28
+ * {
29
+ * name: 'awsAccessKeyId',
30
+ * operation: ParameterOperation.MODIFY,
31
+ * newValue: '123456',
32
+ * previousValue: 'abcdef'
33
+ * }
34
+ * ```
35
+ * 2. Use an if statement to only apply this operation for the parameter `awsAccessKeyId`
36
+ * 3. Update the value of the `aws_access_key_id` to the `newValue` specified in the parameter change
37
+ *
38
+ * @param pc ParameterChange, the parameter name and values to modify on the resource
39
+ * @param plan The overall plan that triggered the modify operation
40
+ */
41
+ async modify(pc, plan) {
42
+ }
43
+ ;
44
+ }