From 98de98616ca1bb604e2b1814bf2599ba799945e9 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sat, 13 Dec 2025 19:36:49 -0500 Subject: [PATCH 1/2] feat: Add support for sensitive parameters --- package-lock.json | 8 +++---- package.json | 2 +- src/plan/change-set.test.ts | 2 +- src/plan/change-set.ts | 19 +++++++++++++--- src/plan/plan.ts | 11 ++++++---- src/plugin/plugin.ts | 10 ++++++++- .../resource-controller-stateful-mode.test.ts | 22 +++++++++++++------ src/resource/resource-controller.test.ts | 6 +++-- src/resource/resource-settings.test.ts | 2 ++ src/resource/resource-settings.ts | 9 +++++++- 10 files changed, 67 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 14b4d05..a7605b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "ajv": "^8.12.0", "ajv-formats": "^2.1.1", "clean-deep": "^3.4.0", - "codify-schemas": "1.0.77", + "codify-schemas": "1.0.83", "lodash.isequal": "^4.5.0", "nanoid": "^5.0.9", "strip-ansi": "^7.1.0", @@ -2297,9 +2297,9 @@ } }, "node_modules/codify-schemas": { - "version": "1.0.77", - "resolved": "https://registry.npmjs.org/codify-schemas/-/codify-schemas-1.0.77.tgz", - "integrity": "sha512-Xv4M/2k9e6pIbrI6NGDeUYsRjBHET45x0ygBxhpgLcIgGGeNnWVi5/Mh1mTn+TOwGmrAw3bTRsQCpDLmtfJeGA==", + "version": "1.0.83", + "resolved": "https://registry.npmjs.org/codify-schemas/-/codify-schemas-1.0.83.tgz", + "integrity": "sha512-QNMYhOt/V78Z+F1eLsSodNcmn8dLhLGlWa3rEzrBl7Ah8E2hylmsK7dEjkBWytoqEB2DISt8RuULG/FmL32/0g==", "license": "ISC", "dependencies": { "ajv": "^8.12.0" diff --git a/package.json b/package.json index 912667a..d156421 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^2.1.1", - "codify-schemas": "1.0.77", + "codify-schemas": "1.0.83", "@npmcli/promise-spawn": "^7.0.1", "@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5", "uuid": "^10.0.0", diff --git a/src/plan/change-set.test.ts b/src/plan/change-set.test.ts index d3a219f..9af13a7 100644 --- a/src/plan/change-set.test.ts +++ b/src/plan/change-set.test.ts @@ -87,7 +87,7 @@ describe('Change set tests', () => { it('Correctly diffs two resource configs (destory)', () => { const cs = ChangeSet.destroy({ propA: 'prop', - propB: 'propB' + propB: 'propB', }); expect(cs.parameterChanges.length).to.eq(2); diff --git a/src/plan/change-set.ts b/src/plan/change-set.ts index 012ccc8..ea400a2 100644 --- a/src/plan/change-set.ts +++ b/src/plan/change-set.ts @@ -1,6 +1,7 @@ import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas'; import { ParsedParameterSetting } from '../resource/parsed-resource-settings.js'; +import { ResourceSettings } from '../resource/resource-settings.js'; /** * A parameter change describes a parameter level change to a resource. @@ -25,6 +26,11 @@ export interface ParameterChange { * The new value of the resource (the desired value) */ newValue: any | null; + + /** + * Whether the parameter is sensitive + */ + isSensitive: boolean; } // Change set will coerce undefined values to null because undefined is not valid JSON @@ -60,37 +66,40 @@ export class ChangeSet { return new ChangeSet(ResourceOperation.NOOP, []); } - static create(desired: Partial): ChangeSet { + static create(desired: Partial, settings?: ResourceSettings): ChangeSet { const parameterChanges = Object.entries(desired) .map(([k, v]) => ({ name: k, operation: ParameterOperation.ADD, previousValue: null, newValue: v ?? null, + isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false, })) return new ChangeSet(ResourceOperation.CREATE, parameterChanges); } - static noop(parameters: Partial): ChangeSet { + static noop(parameters: Partial, settings?: ResourceSettings): ChangeSet { const parameterChanges = Object.entries(parameters) .map(([k, v]) => ({ name: k, operation: ParameterOperation.NOOP, previousValue: v ?? null, newValue: v ?? null, + isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false, })) return new ChangeSet(ResourceOperation.NOOP, parameterChanges); } - static destroy(current: Partial): ChangeSet { + static destroy(current: Partial, settings?: ResourceSettings): ChangeSet { const parameterChanges = Object.entries(current) .map(([k, v]) => ({ name: k, operation: ParameterOperation.REMOVE, previousValue: v ?? null, newValue: null, + isSensitive: settings?.parameterSettings?.[k]?.isSensitive ?? false, })) return new ChangeSet(ResourceOperation.DESTROY, parameterChanges); @@ -160,6 +169,7 @@ export class ChangeSet { previousValue: current[k] ?? null, newValue: desired[k] ?? null, operation: ParameterOperation.NOOP, + isSensitive: parameterOptions?.[k]?.isSensitive ?? false, }) continue; @@ -171,6 +181,7 @@ export class ChangeSet { previousValue: current[k] ?? null, newValue: null, operation: ParameterOperation.REMOVE, + isSensitive: parameterOptions?.[k]?.isSensitive ?? false, }) continue; @@ -182,6 +193,7 @@ export class ChangeSet { previousValue: null, newValue: desired[k] ?? null, operation: ParameterOperation.ADD, + isSensitive: parameterOptions?.[k]?.isSensitive ?? false, }) continue; @@ -192,6 +204,7 @@ export class ChangeSet { previousValue: current[k] ?? null, newValue: desired[k] ?? null, operation: ParameterOperation.MODIFY, + isSensitive: parameterOptions?.[k]?.isSensitive ?? false, }) } diff --git a/src/plan/plan.ts b/src/plan/plan.ts index 952416f..54497f7 100644 --- a/src/plan/plan.ts +++ b/src/plan/plan.ts @@ -118,7 +118,7 @@ export class Plan { if (!filteredCurrentParameters && desired) { return new Plan( uuidV4(), - ChangeSet.create(desired), + ChangeSet.create(desired, settings), core, isStateful, ) @@ -130,7 +130,7 @@ export class Plan { if (!settings.canDestroy) { return new Plan( uuidV4(), - ChangeSet.noop(filteredCurrentParameters), + ChangeSet.noop(filteredCurrentParameters, settings), core, isStateful, ) @@ -138,7 +138,7 @@ export class Plan { return new Plan( uuidV4(), - ChangeSet.destroy(filteredCurrentParameters), + ChangeSet.destroy(filteredCurrentParameters, settings), core, isStateful, ) @@ -171,7 +171,10 @@ export class Plan { uuidV4(), new ChangeSet( data.operation, - data.parameters + data.parameters.map((p) => ({ + ...p, + isSensitive: p.isSensitive ?? false, + })), ), { type: data.resourceType, diff --git a/src/plugin/plugin.ts b/src/plugin/plugin.ts index f53fb9a..5768ab2 100644 --- a/src/plugin/plugin.ts +++ b/src/plugin/plugin.ts @@ -62,11 +62,14 @@ export class Plugin { .map((r) => ({ dependencies: r.dependencies, type: r.typeId, + sensitiveParameters: Object.entries(r.settings.parameterSettings ?? {}) + .filter(([, v]) => v?.isSensitive) + .map(([k]) => k), })) } } - async getResourceInfo(data: GetResourceInfoRequestData): Promise { + getResourceInfo(data: GetResourceInfoRequestData): GetResourceInfoResponseData { if (!this.resourceControllers.has(data.type)) { throw new Error(`Cannot get info for resource ${data.type}, resource doesn't exist`); } @@ -84,6 +87,10 @@ export class Plugin { const allowMultiple = resource.settings.allowMultiple !== undefined && resource.settings.allowMultiple !== false; + const sensitiveParameters = Object.entries(resource.settings.parameterSettings ?? {}) + .filter(([, v]) => v?.isSensitive) + .map(([k]) => k); + return { plugin: this.name, type: data.type, @@ -96,6 +103,7 @@ export class Plugin { import: { requiredParameters: requiredPropertyNames, }, + sensitiveParameters, allowMultiple } } diff --git a/src/resource/resource-controller-stateful-mode.test.ts b/src/resource/resource-controller-stateful-mode.test.ts index bffc215..e2c9a0b 100644 --- a/src/resource/resource-controller-stateful-mode.test.ts +++ b/src/resource/resource-controller-stateful-mode.test.ts @@ -142,19 +142,23 @@ describe('Resource tests for stateful plans', () => { name: "propA", newValue: "propA", previousValue: "propA", - operation: ParameterOperation.NOOP + operation: ParameterOperation.NOOP, + isSensitive: false, + }, { name: "propB", newValue: 10, previousValue: null, - operation: ParameterOperation.ADD + operation: ParameterOperation.ADD, + isSensitive: false, }, { name: "propC", newValue: 'propC', previousValue: 'propC', - operation: ParameterOperation.NOOP + operation: ParameterOperation.NOOP, + isSensitive: false, }, ]) }, @@ -214,25 +218,29 @@ describe('Resource tests for stateful plans', () => { name: "propA", newValue: "propA", previousValue: "propA", - operation: ParameterOperation.NOOP + operation: ParameterOperation.NOOP, + isSensitive: false, }, { name: "propB", newValue: 10, previousValue: null, - operation: ParameterOperation.ADD + operation: ParameterOperation.ADD, + isSensitive: false, }, { name: "propC", newValue: 'propC', previousValue: 'propC', - operation: ParameterOperation.NOOP + operation: ParameterOperation.NOOP, + isSensitive: false, }, { name: "propD", newValue: 'propD', previousValue: null, - operation: ParameterOperation.ADD + operation: ParameterOperation.ADD, + isSensitive: false, }, ]) }, diff --git a/src/resource/resource-controller.test.ts b/src/resource/resource-controller.test.ts index b5c0eb7..df3cc7d 100644 --- a/src/resource/resource-controller.test.ts +++ b/src/resource/resource-controller.test.ts @@ -80,13 +80,15 @@ describe('Resource tests', () => { name: 'propA', previousValue: 'propABefore', newValue: 'propA', - operation: 'modify' + operation: 'modify', + isSensitive: false, }) expect(result.changeSet.parameterChanges[1]).to.deep.eq({ name: 'propB', previousValue: 10, newValue: 10, - operation: 'noop' + operation: 'noop', + isSensitive: false, }) }) diff --git a/src/resource/resource-settings.test.ts b/src/resource/resource-settings.test.ts index c53ca7d..77a1417 100644 --- a/src/resource/resource-settings.test.ts +++ b/src/resource/resource-settings.test.ts @@ -717,12 +717,14 @@ describe('Resource parameter tests', () => { operation: ParameterOperation.NOOP, previousValue: null, newValue: 'setting', + isSensitive: false, }, { name: 'propB', operation: ParameterOperation.NOOP, previousValue: 64, newValue: 64, + isSensitive: false, } ]) ) diff --git a/src/resource/resource-settings.ts b/src/resource/resource-settings.ts index 1414299..98ab70f 100644 --- a/src/resource/resource-settings.ts +++ b/src/resource/resource-settings.ts @@ -163,7 +163,7 @@ export interface ResourceSettings { * @param input * @param context */ - refreshMapper?: (input: Partial, context: RefreshContext) => Partial + refreshMapper?: (input: Partial, context: RefreshContext) => Partial; } } @@ -207,6 +207,13 @@ export interface DefaultParameterSetting { */ type?: ParameterSettingType; + /** + * Mark the field as sensitive. Defaults to false. This has two side effects: + * 1. When displaying this field in the plan, it will be replaced with asterisks + * 2. When importing, resources with sensitive fields will be skipped unless the user explicitly allows it. + */ + isSensitive?: boolean; + /** * Default value for the parameter. If a value is not provided in the config, then this value will be used. */ From 1e3f1e90e426cb0d680bc5430f313b56ec10c3dd Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sat, 13 Dec 2025 20:18:10 -0500 Subject: [PATCH 2/2] feat: Add resource level isSensitive --- package.json | 2 +- src/plugin/plugin.ts | 30 ++++++++++++++++++++++++------ src/resource/resource-settings.ts | 6 ++++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index d156421..49aa106 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codify-plugin-lib", - "version": "1.0.177", + "version": "1.0.180", "description": "Library plugin library", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/plugin/plugin.ts b/src/plugin/plugin.ts index 5768ab2..4ab5045 100644 --- a/src/plugin/plugin.ts +++ b/src/plugin/plugin.ts @@ -59,13 +59,24 @@ export class Plugin { return { resourceDefinitions: [...this.resourceControllers.values()] - .map((r) => ({ - dependencies: r.dependencies, - type: r.typeId, - sensitiveParameters: Object.entries(r.settings.parameterSettings ?? {}) + .map((r) => { + const sensitiveParameters = Object.entries(r.settings.parameterSettings ?? {}) .filter(([, v]) => v?.isSensitive) - .map(([k]) => k), - })) + .map(([k]) => k); + + // Here we add '*' if the resource is sensitive but no sensitive parameters are found. This works because the import + // sensitivity check only checks for the existance of a sensitive parameter whereas the parameter blocking one blocks + // on a specific sensitive parameter. + if (r.settings.isSensitive && sensitiveParameters.length === 0) { + sensitiveParameters.push('*'); + } + + return { + dependencies: r.dependencies, + type: r.typeId, + sensitiveParameters, + } + }) } } @@ -87,10 +98,17 @@ export class Plugin { const allowMultiple = resource.settings.allowMultiple !== undefined && resource.settings.allowMultiple !== false; + // Here we add '*' if the resource is sensitive but no sensitive parameters are found. This works because the import + // sensitivity check only checks for the existance of a sensitive parameter whereas the parameter blocking one blocks + // on a specific sensitive parameter. const sensitiveParameters = Object.entries(resource.settings.parameterSettings ?? {}) .filter(([, v]) => v?.isSensitive) .map(([k]) => k); + if (resource.settings.isSensitive && sensitiveParameters.length === 0) { + sensitiveParameters.push('*'); + } + return { plugin: this.name, type: data.type, diff --git a/src/resource/resource-settings.ts b/src/resource/resource-settings.ts index 98ab70f..70bafe4 100644 --- a/src/resource/resource-settings.ts +++ b/src/resource/resource-settings.ts @@ -27,6 +27,12 @@ export interface ResourceSettings { */ schema?: Partial>; + /** + * Mark the resource as sensitive. Defaults to false. This prevents the resource from automatically being imported by init and import. + * This differs from the parameter level sensitivity which also prevents the parameter value from being displayed in the plan. + */ + isSensitive?: boolean; + /** * Allow multiple of the same resource to unique. Set truthy if * multiples are allowed, for example for applications, there can be multiple copy of the same application installed