codify-plugin-lib 1.0.177 → 1.0.179

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.
@@ -4,6 +4,7 @@
4
4
  declare class CodifyCliSenderImpl {
5
5
  private readonly validateIpcMessageV2;
6
6
  requestPressKeyToContinuePrompt(message?: string): Promise<void>;
7
+ getCodifyCliCredentials(): Promise<string>;
7
8
  private sendAndWaitForResponse;
8
9
  }
9
10
  export declare const CodifyCliSender: CodifyCliSenderImpl;
@@ -17,6 +17,16 @@ class CodifyCliSenderImpl {
17
17
  }
18
18
  });
19
19
  }
20
+ async getCodifyCliCredentials() {
21
+ const data = await this.sendAndWaitForResponse({
22
+ cmd: MessageCmd.CODIFY_CREDENTIALS_REQUEST,
23
+ data: {},
24
+ });
25
+ if (typeof data.data !== 'string') {
26
+ throw new Error('Expected string back from credentials request');
27
+ }
28
+ return data.data;
29
+ }
20
30
  async sendAndWaitForResponse(message) {
21
31
  return new Promise((resolve) => {
22
32
  const requestId = nanoid(8);
@@ -1,5 +1,6 @@
1
1
  import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
2
2
  import { ParsedParameterSetting } from '../resource/parsed-resource-settings.js';
3
+ import { ResourceSettings } from '../resource/resource-settings.js';
3
4
  /**
4
5
  * A parameter change describes a parameter level change to a resource.
5
6
  */
@@ -20,6 +21,10 @@ export interface ParameterChange<T extends StringIndexedObject> {
20
21
  * The new value of the resource (the desired value)
21
22
  */
22
23
  newValue: any | null;
24
+ /**
25
+ * Whether the parameter is sensitive
26
+ */
27
+ isSensitive: boolean;
23
28
  }
24
29
  export declare class ChangeSet<T extends StringIndexedObject> {
25
30
  operation: ResourceOperation;
@@ -28,9 +33,9 @@ export declare class ChangeSet<T extends StringIndexedObject> {
28
33
  get desiredParameters(): T;
29
34
  get currentParameters(): T;
30
35
  static empty<T extends StringIndexedObject>(): ChangeSet<T>;
31
- static create<T extends StringIndexedObject>(desired: Partial<T>): ChangeSet<T>;
32
- static noop<T extends StringIndexedObject>(parameters: Partial<T>): ChangeSet<T>;
33
- static destroy<T extends StringIndexedObject>(current: Partial<T>): ChangeSet<T>;
36
+ static create<T extends StringIndexedObject>(desired: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T>;
37
+ static noop<T extends StringIndexedObject>(parameters: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T>;
38
+ static destroy<T extends StringIndexedObject>(current: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T>;
34
39
  static calculateModification<T extends StringIndexedObject>(desired: Partial<T>, current: Partial<T>, parameterSettings?: Partial<Record<keyof T, ParsedParameterSetting>>): ChangeSet<T>;
35
40
  /**
36
41
  * Calculates the differences between the desired and current parameters,
@@ -24,33 +24,36 @@ export class ChangeSet {
24
24
  static empty() {
25
25
  return new ChangeSet(ResourceOperation.NOOP, []);
26
26
  }
27
- static create(desired) {
27
+ static create(desired, settings) {
28
28
  const parameterChanges = Object.entries(desired)
29
29
  .map(([k, v]) => ({
30
30
  name: k,
31
31
  operation: ParameterOperation.ADD,
32
32
  previousValue: null,
33
33
  newValue: v ?? null,
34
+ isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
34
35
  }));
35
36
  return new ChangeSet(ResourceOperation.CREATE, parameterChanges);
36
37
  }
37
- static noop(parameters) {
38
+ static noop(parameters, settings) {
38
39
  const parameterChanges = Object.entries(parameters)
39
40
  .map(([k, v]) => ({
40
41
  name: k,
41
42
  operation: ParameterOperation.NOOP,
42
43
  previousValue: v ?? null,
43
44
  newValue: v ?? null,
45
+ isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
44
46
  }));
45
47
  return new ChangeSet(ResourceOperation.NOOP, parameterChanges);
46
48
  }
47
- static destroy(current) {
49
+ static destroy(current, settings) {
48
50
  const parameterChanges = Object.entries(current)
49
51
  .map(([k, v]) => ({
50
52
  name: k,
51
53
  operation: ParameterOperation.REMOVE,
52
54
  previousValue: v ?? null,
53
55
  newValue: null,
56
+ isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
54
57
  }));
55
58
  return new ChangeSet(ResourceOperation.DESTROY, parameterChanges);
56
59
  }
@@ -98,6 +101,7 @@ export class ChangeSet {
98
101
  previousValue: current[k] ?? null,
99
102
  newValue: desired[k] ?? null,
100
103
  operation: ParameterOperation.NOOP,
104
+ isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
101
105
  });
102
106
  continue;
103
107
  }
@@ -107,6 +111,7 @@ export class ChangeSet {
107
111
  previousValue: current[k] ?? null,
108
112
  newValue: null,
109
113
  operation: ParameterOperation.REMOVE,
114
+ isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
110
115
  });
111
116
  continue;
112
117
  }
@@ -116,6 +121,7 @@ export class ChangeSet {
116
121
  previousValue: null,
117
122
  newValue: desired[k] ?? null,
118
123
  operation: ParameterOperation.ADD,
124
+ isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
119
125
  });
120
126
  continue;
121
127
  }
@@ -124,6 +130,7 @@ export class ChangeSet {
124
130
  previousValue: current[k] ?? null,
125
131
  newValue: desired[k] ?? null,
126
132
  operation: ParameterOperation.MODIFY,
133
+ isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
127
134
  });
128
135
  }
129
136
  return parameterChangeSet;
package/dist/plan/plan.js CHANGED
@@ -68,15 +68,15 @@ export class Plan {
68
68
  }
69
69
  // CREATE
70
70
  if (!filteredCurrentParameters && desired) {
71
- return new Plan(uuidV4(), ChangeSet.create(desired), core, isStateful);
71
+ return new Plan(uuidV4(), ChangeSet.create(desired, settings), core, isStateful);
72
72
  }
73
73
  // DESTROY
74
74
  if (filteredCurrentParameters && !desired) {
75
75
  // We can manually override destroys. If a resource cannot be destroyed (for instance the npm resource relies on NodeJS being created and destroyed)
76
76
  if (!settings.canDestroy) {
77
- return new Plan(uuidV4(), ChangeSet.noop(filteredCurrentParameters), core, isStateful);
77
+ return new Plan(uuidV4(), ChangeSet.noop(filteredCurrentParameters, settings), core, isStateful);
78
78
  }
79
- return new Plan(uuidV4(), ChangeSet.destroy(filteredCurrentParameters), core, isStateful);
79
+ return new Plan(uuidV4(), ChangeSet.destroy(filteredCurrentParameters, settings), core, isStateful);
80
80
  }
81
81
  // NO-OP, MODIFY or RE-CREATE
82
82
  const changeSet = ChangeSet.calculateModification(desired, filteredCurrentParameters, settings.parameterSettings);
@@ -88,7 +88,10 @@ export class Plan {
88
88
  throw new Error('Data is empty');
89
89
  }
90
90
  addDefaultValues();
91
- return new Plan(uuidV4(), new ChangeSet(data.operation, data.parameters), {
91
+ return new Plan(uuidV4(), new ChangeSet(data.operation, data.parameters.map((p) => ({
92
+ ...p,
93
+ isSensitive: p.isSensitive ?? false,
94
+ }))), {
92
95
  type: data.resourceType,
93
96
  name: data.resourceName,
94
97
  }, data.isStateful);
@@ -11,7 +11,7 @@ export declare class Plugin {
11
11
  constructor(name: string, resourceControllers: Map<string, ResourceController<ResourceConfig>>);
12
12
  static create(name: string, resources: Resource<any>[]): Plugin;
13
13
  initialize(data: InitializeRequestData): Promise<InitializeResponseData>;
14
- getResourceInfo(data: GetResourceInfoRequestData): Promise<GetResourceInfoResponseData>;
14
+ getResourceInfo(data: GetResourceInfoRequestData): GetResourceInfoResponseData;
15
15
  match(data: MatchRequestData): Promise<MatchResponseData>;
16
16
  import(data: ImportRequestData): Promise<ImportResponseData>;
17
17
  validate(data: ValidateRequestData): Promise<ValidateResponseData>;
@@ -33,10 +33,13 @@ export class Plugin {
33
33
  .map((r) => ({
34
34
  dependencies: r.dependencies,
35
35
  type: r.typeId,
36
+ sensitiveParameters: Object.entries(r.settings.parameterSettings ?? {})
37
+ .filter(([, v]) => v?.isSensitive)
38
+ .map(([k]) => k),
36
39
  }))
37
40
  };
38
41
  }
39
- async getResourceInfo(data) {
42
+ getResourceInfo(data) {
40
43
  if (!this.resourceControllers.has(data.type)) {
41
44
  throw new Error(`Cannot get info for resource ${data.type}, resource doesn't exist`);
42
45
  }
@@ -48,6 +51,9 @@ export class Plugin {
48
51
  ?? undefined);
49
52
  const allowMultiple = resource.settings.allowMultiple !== undefined
50
53
  && resource.settings.allowMultiple !== false;
54
+ const sensitiveParameters = Object.entries(resource.settings.parameterSettings ?? {})
55
+ .filter(([, v]) => v?.isSensitive)
56
+ .map(([k]) => k);
51
57
  return {
52
58
  plugin: this.name,
53
59
  type: data.type,
@@ -60,6 +66,7 @@ export class Plugin {
60
66
  import: {
61
67
  requiredParameters: requiredPropertyNames,
62
68
  },
69
+ sensitiveParameters,
63
70
  allowMultiple
64
71
  };
65
72
  }
@@ -24,7 +24,7 @@ export declare class ResourceController<T extends StringIndexedObject> {
24
24
  private applyModify;
25
25
  private applyDestroy;
26
26
  private validateRefreshResults;
27
- private applyTransformParameters;
27
+ private applyTransformations;
28
28
  private addDefaultValues;
29
29
  private removeDefaultValues;
30
30
  private refreshNonStatefulParameters;
@@ -33,7 +33,7 @@ export class ResourceController {
33
33
  }
34
34
  async validate(core, parameters) {
35
35
  const originalParameters = structuredClone(parameters);
36
- await this.applyTransformParameters(parameters);
36
+ await this.applyTransformations(parameters, undefined, true);
37
37
  this.addDefaultValues(parameters);
38
38
  if (this.schemaValidator) {
39
39
  // Schema validator uses pre transformation parameters
@@ -103,9 +103,9 @@ export class ResourceController {
103
103
  const originalParams = structuredClone(resource.parameters);
104
104
  const paramsToMatch = structuredClone(resourceToMatch.parameters);
105
105
  this.addDefaultValues(originalParams);
106
- await this.applyTransformParameters(originalParams);
106
+ await this.applyTransformations(originalParams);
107
107
  this.addDefaultValues(paramsToMatch);
108
- await this.applyTransformParameters(paramsToMatch);
108
+ await this.applyTransformations(paramsToMatch);
109
109
  const match = parameterMatcher(originalParams, paramsToMatch);
110
110
  if (match) {
111
111
  return resourceToMatch;
@@ -120,9 +120,9 @@ export class ResourceController {
120
120
  originalDesiredConfig: structuredClone(desired),
121
121
  };
122
122
  this.addDefaultValues(desired);
123
- await this.applyTransformParameters(desired);
123
+ await this.applyTransformations(desired);
124
124
  this.addDefaultValues(state);
125
- await this.applyTransformParameters(state);
125
+ await this.applyTransformations(state);
126
126
  // Parse data from the user supplied config
127
127
  const parsedConfig = new ConfigParser(desired, state, this.parsedSettings.statefulParameters);
128
128
  const { allParameters, allNonStatefulParameters, allStatefulParameters, } = parsedConfig;
@@ -156,7 +156,7 @@ export class ResourceController {
156
156
  }
157
157
  async planDestroy(core, parameters) {
158
158
  this.addDefaultValues(parameters);
159
- await this.applyTransformParameters(parameters);
159
+ await this.applyTransformations(parameters);
160
160
  // Use refresh parameters if specified, otherwise try to refresh as many parameters as possible here
161
161
  const parametersToRefresh = this.settings.importAndDestroy?.refreshKeys
162
162
  ? {
@@ -211,7 +211,7 @@ export class ResourceController {
211
211
  return results.filter(Boolean).flat();
212
212
  }
213
213
  this.addDefaultValues(parameters);
214
- await this.applyTransformParameters(parameters);
214
+ await this.applyTransformations(parameters);
215
215
  // Use refresh parameters if specified, otherwise try to refresh as many parameters as possible here
216
216
  const parametersToRefresh = this.getParametersToRefreshForImport(parameters, context);
217
217
  // Parse data from the user supplied config
@@ -227,7 +227,7 @@ export class ResourceController {
227
227
  const resultParametersArray = currentParametersArray
228
228
  ?.map((r, idx) => ({ ...r, ...statefulCurrentParameters[idx] }));
229
229
  for (const result of resultParametersArray) {
230
- await this.applyTransformParameters(result, { original: context.originalDesiredConfig });
230
+ await this.applyTransformations(result, { original: context.originalDesiredConfig });
231
231
  this.removeDefaultValues(result, parameters);
232
232
  }
233
233
  return resultParametersArray?.map((r) => ({ core, parameters: r }));
@@ -292,7 +292,7 @@ ${JSON.stringify(refresh, null, 2)}
292
292
  `);
293
293
  }
294
294
  }
295
- async applyTransformParameters(config, reverse) {
295
+ async applyTransformations(config, reverse, skipConfigTransformation = false) {
296
296
  if (!config) {
297
297
  return;
298
298
  }
@@ -304,7 +304,7 @@ ${JSON.stringify(refresh, null, 2)}
304
304
  ? await inputTransformation.from(config[key], reverse.original?.[key])
305
305
  : await inputTransformation.to(config[key]);
306
306
  }
307
- if (this.settings.transformation) {
307
+ if (this.settings.transformation && !skipConfigTransformation) {
308
308
  const transformed = reverse
309
309
  ? await this.settings.transformation.from({ ...config }, reverse.original)
310
310
  : await this.settings.transformation.to({ ...config });
@@ -167,6 +167,12 @@ export interface DefaultParameterSetting {
167
167
  * is mainly used to determine the equality method when performing diffing.
168
168
  */
169
169
  type?: ParameterSettingType;
170
+ /**
171
+ * Mark the field as sensitive. Defaults to false. This has two side effects:
172
+ * 1. When displaying this field in the plan, it will be replaced with asterisks
173
+ * 2. When importing, resources with sensitive fields will be skipped unless the user explicitly allows it.
174
+ */
175
+ isSensitive?: boolean;
170
176
  /**
171
177
  * Default value for the parameter. If a value is not provided in the config, then this value will be used.
172
178
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codify-plugin-lib",
3
- "version": "1.0.177",
3
+ "version": "1.0.179",
4
4
  "description": "Library plugin library",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -16,7 +16,7 @@
16
16
  "dependencies": {
17
17
  "ajv": "^8.12.0",
18
18
  "ajv-formats": "^2.1.1",
19
- "codify-schemas": "1.0.76",
19
+ "codify-schemas": "1.0.83",
20
20
  "@npmcli/promise-spawn": "^7.0.1",
21
21
  "@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5",
22
22
  "uuid": "^10.0.0",
@@ -21,6 +21,19 @@ class CodifyCliSenderImpl {
21
21
  })
22
22
  }
23
23
 
24
+ async getCodifyCliCredentials(): Promise<string> {
25
+ const data = await this.sendAndWaitForResponse(<IpcMessageV2>{
26
+ cmd: MessageCmd.CODIFY_CREDENTIALS_REQUEST,
27
+ data: {},
28
+ })
29
+
30
+ if (typeof data.data !== 'string') {
31
+ throw new Error('Expected string back from credentials request');
32
+ }
33
+
34
+ return data.data;
35
+ }
36
+
24
37
  private async sendAndWaitForResponse(message: IpcMessageV2): Promise<IpcMessageV2> {
25
38
  return new Promise((resolve) => {
26
39
  const requestId = nanoid(8);
@@ -87,7 +87,7 @@ describe('Change set tests', () => {
87
87
  it('Correctly diffs two resource configs (destory)', () => {
88
88
  const cs = ChangeSet.destroy({
89
89
  propA: 'prop',
90
- propB: 'propB'
90
+ propB: 'propB',
91
91
  });
92
92
 
93
93
  expect(cs.parameterChanges.length).to.eq(2);
@@ -1,6 +1,7 @@
1
1
  import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
2
2
 
3
3
  import { ParsedParameterSetting } from '../resource/parsed-resource-settings.js';
4
+ import { ResourceSettings } from '../resource/resource-settings.js';
4
5
 
5
6
  /**
6
7
  * A parameter change describes a parameter level change to a resource.
@@ -25,6 +26,11 @@ export interface ParameterChange<T extends StringIndexedObject> {
25
26
  * The new value of the resource (the desired value)
26
27
  */
27
28
  newValue: any | null;
29
+
30
+ /**
31
+ * Whether the parameter is sensitive
32
+ */
33
+ isSensitive: boolean;
28
34
  }
29
35
 
30
36
  // Change set will coerce undefined values to null because undefined is not valid JSON
@@ -60,37 +66,40 @@ export class ChangeSet<T extends StringIndexedObject> {
60
66
  return new ChangeSet<T>(ResourceOperation.NOOP, []);
61
67
  }
62
68
 
63
- static create<T extends StringIndexedObject>(desired: Partial<T>): ChangeSet<T> {
69
+ static create<T extends StringIndexedObject>(desired: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T> {
64
70
  const parameterChanges = Object.entries(desired)
65
71
  .map(([k, v]) => ({
66
72
  name: k,
67
73
  operation: ParameterOperation.ADD,
68
74
  previousValue: null,
69
75
  newValue: v ?? null,
76
+ isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
70
77
  }))
71
78
 
72
79
  return new ChangeSet(ResourceOperation.CREATE, parameterChanges);
73
80
  }
74
81
 
75
- static noop<T extends StringIndexedObject>(parameters: Partial<T>): ChangeSet<T> {
82
+ static noop<T extends StringIndexedObject>(parameters: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T> {
76
83
  const parameterChanges = Object.entries(parameters)
77
84
  .map(([k, v]) => ({
78
85
  name: k,
79
86
  operation: ParameterOperation.NOOP,
80
87
  previousValue: v ?? null,
81
88
  newValue: v ?? null,
89
+ isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
82
90
  }))
83
91
 
84
92
  return new ChangeSet(ResourceOperation.NOOP, parameterChanges);
85
93
  }
86
94
 
87
- static destroy<T extends StringIndexedObject>(current: Partial<T>): ChangeSet<T> {
95
+ static destroy<T extends StringIndexedObject>(current: Partial<T>, settings?: ResourceSettings<T>): ChangeSet<T> {
88
96
  const parameterChanges = Object.entries(current)
89
97
  .map(([k, v]) => ({
90
98
  name: k,
91
99
  operation: ParameterOperation.REMOVE,
92
100
  previousValue: v ?? null,
93
101
  newValue: null,
102
+ isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false,
94
103
  }))
95
104
 
96
105
  return new ChangeSet(ResourceOperation.DESTROY, parameterChanges);
@@ -160,6 +169,7 @@ export class ChangeSet<T extends StringIndexedObject> {
160
169
  previousValue: current[k] ?? null,
161
170
  newValue: desired[k] ?? null,
162
171
  operation: ParameterOperation.NOOP,
172
+ isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
163
173
  })
164
174
 
165
175
  continue;
@@ -171,6 +181,7 @@ export class ChangeSet<T extends StringIndexedObject> {
171
181
  previousValue: current[k] ?? null,
172
182
  newValue: null,
173
183
  operation: ParameterOperation.REMOVE,
184
+ isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
174
185
  })
175
186
 
176
187
  continue;
@@ -182,6 +193,7 @@ export class ChangeSet<T extends StringIndexedObject> {
182
193
  previousValue: null,
183
194
  newValue: desired[k] ?? null,
184
195
  operation: ParameterOperation.ADD,
196
+ isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
185
197
  })
186
198
 
187
199
  continue;
@@ -192,6 +204,7 @@ export class ChangeSet<T extends StringIndexedObject> {
192
204
  previousValue: current[k] ?? null,
193
205
  newValue: desired[k] ?? null,
194
206
  operation: ParameterOperation.MODIFY,
207
+ isSensitive: parameterOptions?.[k]?.isSensitive ?? false,
195
208
  })
196
209
  }
197
210
 
package/src/plan/plan.ts CHANGED
@@ -118,7 +118,7 @@ export class Plan<T extends StringIndexedObject> {
118
118
  if (!filteredCurrentParameters && desired) {
119
119
  return new Plan(
120
120
  uuidV4(),
121
- ChangeSet.create(desired),
121
+ ChangeSet.create(desired, settings),
122
122
  core,
123
123
  isStateful,
124
124
  )
@@ -130,7 +130,7 @@ export class Plan<T extends StringIndexedObject> {
130
130
  if (!settings.canDestroy) {
131
131
  return new Plan(
132
132
  uuidV4(),
133
- ChangeSet.noop(filteredCurrentParameters),
133
+ ChangeSet.noop(filteredCurrentParameters, settings),
134
134
  core,
135
135
  isStateful,
136
136
  )
@@ -138,7 +138,7 @@ export class Plan<T extends StringIndexedObject> {
138
138
 
139
139
  return new Plan(
140
140
  uuidV4(),
141
- ChangeSet.destroy(filteredCurrentParameters),
141
+ ChangeSet.destroy(filteredCurrentParameters, settings),
142
142
  core,
143
143
  isStateful,
144
144
  )
@@ -171,7 +171,10 @@ export class Plan<T extends StringIndexedObject> {
171
171
  uuidV4(),
172
172
  new ChangeSet<T>(
173
173
  data.operation,
174
- data.parameters
174
+ data.parameters.map((p) => ({
175
+ ...p,
176
+ isSensitive: p.isSensitive ?? false,
177
+ })),
175
178
  ),
176
179
  {
177
180
  type: data.resourceType,
@@ -62,11 +62,14 @@ export class Plugin {
62
62
  .map((r) => ({
63
63
  dependencies: r.dependencies,
64
64
  type: r.typeId,
65
+ sensitiveParameters: Object.entries(r.settings.parameterSettings ?? {})
66
+ .filter(([, v]) => v?.isSensitive)
67
+ .map(([k]) => k),
65
68
  }))
66
69
  }
67
70
  }
68
71
 
69
- async getResourceInfo(data: GetResourceInfoRequestData): Promise<GetResourceInfoResponseData> {
72
+ getResourceInfo(data: GetResourceInfoRequestData): GetResourceInfoResponseData {
70
73
  if (!this.resourceControllers.has(data.type)) {
71
74
  throw new Error(`Cannot get info for resource ${data.type}, resource doesn't exist`);
72
75
  }
@@ -84,6 +87,10 @@ export class Plugin {
84
87
  const allowMultiple = resource.settings.allowMultiple !== undefined
85
88
  && resource.settings.allowMultiple !== false;
86
89
 
90
+ const sensitiveParameters = Object.entries(resource.settings.parameterSettings ?? {})
91
+ .filter(([, v]) => v?.isSensitive)
92
+ .map(([k]) => k);
93
+
87
94
  return {
88
95
  plugin: this.name,
89
96
  type: data.type,
@@ -96,6 +103,7 @@ export class Plugin {
96
103
  import: {
97
104
  requiredParameters: requiredPropertyNames,
98
105
  },
106
+ sensitiveParameters,
99
107
  allowMultiple
100
108
  }
101
109
  }
@@ -142,19 +142,23 @@ describe('Resource tests for stateful plans', () => {
142
142
  name: "propA",
143
143
  newValue: "propA",
144
144
  previousValue: "propA",
145
- operation: ParameterOperation.NOOP
145
+ operation: ParameterOperation.NOOP,
146
+ isSensitive: false,
147
+
146
148
  },
147
149
  {
148
150
  name: "propB",
149
151
  newValue: 10,
150
152
  previousValue: null,
151
- operation: ParameterOperation.ADD
153
+ operation: ParameterOperation.ADD,
154
+ isSensitive: false,
152
155
  },
153
156
  {
154
157
  name: "propC",
155
158
  newValue: 'propC',
156
159
  previousValue: 'propC',
157
- operation: ParameterOperation.NOOP
160
+ operation: ParameterOperation.NOOP,
161
+ isSensitive: false,
158
162
  },
159
163
  ])
160
164
  },
@@ -214,25 +218,29 @@ describe('Resource tests for stateful plans', () => {
214
218
  name: "propA",
215
219
  newValue: "propA",
216
220
  previousValue: "propA",
217
- operation: ParameterOperation.NOOP
221
+ operation: ParameterOperation.NOOP,
222
+ isSensitive: false,
218
223
  },
219
224
  {
220
225
  name: "propB",
221
226
  newValue: 10,
222
227
  previousValue: null,
223
- operation: ParameterOperation.ADD
228
+ operation: ParameterOperation.ADD,
229
+ isSensitive: false,
224
230
  },
225
231
  {
226
232
  name: "propC",
227
233
  newValue: 'propC',
228
234
  previousValue: 'propC',
229
- operation: ParameterOperation.NOOP
235
+ operation: ParameterOperation.NOOP,
236
+ isSensitive: false,
230
237
  },
231
238
  {
232
239
  name: "propD",
233
240
  newValue: 'propD',
234
241
  previousValue: null,
235
- operation: ParameterOperation.ADD
242
+ operation: ParameterOperation.ADD,
243
+ isSensitive: false,
236
244
  },
237
245
  ])
238
246
  },
@@ -80,13 +80,15 @@ describe('Resource tests', () => {
80
80
  name: 'propA',
81
81
  previousValue: 'propABefore',
82
82
  newValue: 'propA',
83
- operation: 'modify'
83
+ operation: 'modify',
84
+ isSensitive: false,
84
85
  })
85
86
  expect(result.changeSet.parameterChanges[1]).to.deep.eq({
86
87
  name: 'propB',
87
88
  previousValue: 10,
88
89
  newValue: 10,
89
- operation: 'noop'
90
+ operation: 'noop',
91
+ isSensitive: false,
90
92
  })
91
93
  })
92
94
 
@@ -59,7 +59,7 @@ export class ResourceController<T extends StringIndexedObject> {
59
59
  parameters: Partial<T>,
60
60
  ): Promise<ValidateResponseData['resourceValidations'][0]> {
61
61
  const originalParameters = structuredClone(parameters);
62
- await this.applyTransformParameters(parameters);
62
+ await this.applyTransformations(parameters, undefined, true);
63
63
  this.addDefaultValues(parameters);
64
64
 
65
65
  if (this.schemaValidator) {
@@ -143,10 +143,10 @@ export class ResourceController<T extends StringIndexedObject> {
143
143
  const paramsToMatch = structuredClone(resourceToMatch.parameters) as Partial<T>;
144
144
 
145
145
  this.addDefaultValues(originalParams);
146
- await this.applyTransformParameters(originalParams);
146
+ await this.applyTransformations(originalParams);
147
147
 
148
148
  this.addDefaultValues(paramsToMatch);
149
- await this.applyTransformParameters(paramsToMatch);
149
+ await this.applyTransformations(paramsToMatch);
150
150
 
151
151
  const match = parameterMatcher(originalParams, paramsToMatch);
152
152
  if (match) {
@@ -170,10 +170,10 @@ export class ResourceController<T extends StringIndexedObject> {
170
170
  };
171
171
 
172
172
  this.addDefaultValues(desired);
173
- await this.applyTransformParameters(desired);
173
+ await this.applyTransformations(desired);
174
174
 
175
175
  this.addDefaultValues(state);
176
- await this.applyTransformParameters(state);
176
+ await this.applyTransformations(state);
177
177
 
178
178
  // Parse data from the user supplied config
179
179
  const parsedConfig = new ConfigParser(desired, state, this.parsedSettings.statefulParameters)
@@ -221,7 +221,7 @@ export class ResourceController<T extends StringIndexedObject> {
221
221
  parameters: Partial<T>
222
222
  ): Promise<Plan<T>> {
223
223
  this.addDefaultValues(parameters);
224
- await this.applyTransformParameters(parameters);
224
+ await this.applyTransformations(parameters);
225
225
 
226
226
  // Use refresh parameters if specified, otherwise try to refresh as many parameters as possible here
227
227
  const parametersToRefresh = this.settings.importAndDestroy?.refreshKeys
@@ -298,7 +298,7 @@ export class ResourceController<T extends StringIndexedObject> {
298
298
  }
299
299
 
300
300
  this.addDefaultValues(parameters);
301
- await this.applyTransformParameters(parameters);
301
+ await this.applyTransformations(parameters);
302
302
 
303
303
  // Use refresh parameters if specified, otherwise try to refresh as many parameters as possible here
304
304
  const parametersToRefresh = this.getParametersToRefreshForImport(parameters, context);
@@ -325,7 +325,7 @@ export class ResourceController<T extends StringIndexedObject> {
325
325
  ?.map((r, idx) => ({ ...r, ...statefulCurrentParameters[idx] }))
326
326
 
327
327
  for (const result of resultParametersArray) {
328
- await this.applyTransformParameters(result, { original: context.originalDesiredConfig });
328
+ await this.applyTransformations(result, { original: context.originalDesiredConfig });
329
329
  this.removeDefaultValues(result, parameters);
330
330
  }
331
331
 
@@ -408,9 +408,9 @@ ${JSON.stringify(refresh, null, 2)}
408
408
  }
409
409
  }
410
410
 
411
- private async applyTransformParameters(config: Partial<T> | null, reverse?: {
411
+ private async applyTransformations(config: Partial<T> | null, reverse?: {
412
412
  original: Partial<T> | null
413
- }): Promise<void> {
413
+ }, skipConfigTransformation = false): Promise<void> {
414
414
  if (!config) {
415
415
  return;
416
416
  }
@@ -425,7 +425,7 @@ ${JSON.stringify(refresh, null, 2)}
425
425
  : await inputTransformation.to(config[key]);
426
426
  }
427
427
 
428
- if (this.settings.transformation) {
428
+ if (this.settings.transformation && !skipConfigTransformation) {
429
429
  const transformed = reverse
430
430
  ? await this.settings.transformation.from({ ...config }, reverse.original)
431
431
  : await this.settings.transformation.to({ ...config })
@@ -717,12 +717,14 @@ describe('Resource parameter tests', () => {
717
717
  operation: ParameterOperation.NOOP,
718
718
  previousValue: null,
719
719
  newValue: 'setting',
720
+ isSensitive: false,
720
721
  },
721
722
  {
722
723
  name: 'propB',
723
724
  operation: ParameterOperation.NOOP,
724
725
  previousValue: 64,
725
726
  newValue: 64,
727
+ isSensitive: false,
726
728
  }
727
729
  ])
728
730
  )
@@ -163,7 +163,7 @@ export interface ResourceSettings<T extends StringIndexedObject> {
163
163
  * @param input
164
164
  * @param context
165
165
  */
166
- refreshMapper?: (input: Partial<T>, context: RefreshContext<T>) => Partial<T>
166
+ refreshMapper?: (input: Partial<T>, context: RefreshContext<T>) => Partial<T>;
167
167
  }
168
168
  }
169
169
 
@@ -207,6 +207,13 @@ export interface DefaultParameterSetting {
207
207
  */
208
208
  type?: ParameterSettingType;
209
209
 
210
+ /**
211
+ * Mark the field as sensitive. Defaults to false. This has two side effects:
212
+ * 1. When displaying this field in the plan, it will be replaced with asterisks
213
+ * 2. When importing, resources with sensitive fields will be skipped unless the user explicitly allows it.
214
+ */
215
+ isSensitive?: boolean;
216
+
210
217
  /**
211
218
  * Default value for the parameter. If a value is not provided in the config, then this value will be used.
212
219
  */