Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1pass plugin improvements #125

Merged
merged 10 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/big-swans-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@dmno/encrypted-vault-plugin": patch
"@dmno/1password-plugin": patch
"dmno": patch
---

1password plugin improvements, related refactoring
46 changes: 30 additions & 16 deletions example-repo/.dmno/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,26 @@ import { EncryptedVaultDmnoPlugin, EncryptedVaultTypes } from '@dmno/encrypted-v
const OnePassSecretsProd = new OnePasswordDmnoPlugin('1pass/prod', {
token: configPath('OP_TOKEN'),
envItemLink: 'https://start.1password.com/open/i?a=I3GUA2KU6BD3FBHA47QNBIVEV4&v=ut2dftalm3ugmxc6klavms6tfq&i=n4wmgfq77mydg5lebtroa3ykvm&h=dmnoinc.1password.com',

// token: InjectPluginInputByType,
// token: 'asdf',
});
const OnePassSecretsDev = new OnePasswordDmnoPlugin('1pass', {
token: configPath('OP_TOKEN'),
envItemLink: 'https://start.1password.com/open/i?a=I3GUA2KU6BD3FBHA47QNBIVEV4&v=ut2dftalm3ugmxc6klavms6tfq&i=n4wmgfq77mydg5lebtroa3ykvm&h=dmnoinc.1password.com',
envItemLink: 'https://start.1password.com/open/i?a=I3GUA2KU6BD3FBHA47QNBIVEV4&v=ut2dftalm3ugmxc6klavms6tfq&i=4u4klfhpldobgdxrcjwb2bqsta&h=dmnoinc.1password.com',
// token: InjectPluginInputByType,
// token: 'asdf',
});


const ProdVault = new EncryptedVaultDmnoPlugin('vault/prod', {
const EncryptedVaultSecrets = new EncryptedVaultDmnoPlugin('vault/prod', {
key: configPath('DMNO_VAULT_KEY'),
name: 'prod',
});
const NonProdVault = new EncryptedVaultDmnoPlugin('vault/dev', {
key: configPath('DMNO_VAULT_KEY'),
name: 'dev',
});
// const NonProdVault = new EncryptedVaultDmnoPlugin('vault/dev', {
// key: configPath('DMNO_VAULT_KEY'),
// name: 'dev',
// });



Expand All @@ -47,8 +48,24 @@ export default defineDmnoService({
OP_TOKEN: {
extends: OnePasswordTypes.serviceAccountToken,
},
OP_TOKEN_PROD: {
extends: OnePasswordTypes.serviceAccountToken,
// OP_TOKEN_PROD: {
// extends: OnePasswordTypes.serviceAccountToken,
// },

OP_ITEM_1: {
value: switchBy('DMNO_ENV', {
_default: OnePassSecretsDev.item(),
production: OnePassSecretsProd.item(),
}),
},
OP_ITEM_BY_ID: {
value: OnePassSecretsDev.itemById("ut2dftalm3ugmxc6klavms6tfq", "bphvvrqjegfmd5yoz4buw2aequ", "username"),
},
OP_ITEM_BY_LINK: {
value: OnePassSecretsDev.itemByLink("https://start.1password.com/open/i?a=I3GUA2KU6BD3FBHA47QNBIVEV4&v=ut2dftalm3ugmxc6klavms6tfq&i=bphvvrqjegfmd5yoz4buw2aequ&h=dmnoinc.1password.com", "helturjryuy73yjbnaovlce5fu"),
},
OP_ITEM_BY_REFERENCE: {
value: OnePassSecretsDev.itemByReference("op://dev test/example/username"),
},

SOME_API_KEY: {
Expand All @@ -58,8 +75,6 @@ export default defineDmnoService({
}),
},



DMNO_VAULT_KEY: {
extends: EncryptedVaultTypes.encryptionKey,
// required: true
Expand All @@ -71,16 +86,16 @@ export default defineDmnoService({
},

VAULT_ITEM_1: {
value: ProdVault.item(),
value: EncryptedVaultSecrets.item(),
},
VAULT_ITEM_WITH_SWITCH: {
value: switchByNodeEnv({
_default: NonProdVault.item(),
_default: EncryptedVaultSecrets.item(),
staging: switchBy('CONTEXT', {
'branch-preview': ProdVault.item(),
'pr-preview': ProdVault.item(),
'branch-preview': EncryptedVaultSecrets.item(),
'pr-preview': EncryptedVaultSecrets.item(),
}),
production: ProdVault.item()
production: EncryptedVaultSecrets.item()
}),
},

Expand All @@ -92,7 +107,6 @@ export default defineDmnoService({
extends: DmnoBaseTypes.email({
normalize: true,
}),
// required: true,
value: '[email protected]'
},

Expand Down
1 change: 1 addition & 0 deletions example-repo/packages/astro-web/.dmno/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default defineDmnoService({
source: 'api',
key: 'API_URL',
},
'SOME_API_KEY',
],
schema: {
OP_TOKEN: { extends: OnePasswordTypes.serviceAccountToken },
Expand Down
4 changes: 2 additions & 2 deletions example-repo/packages/astro-web/astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ console.log('> secret value =', DMNO_CONFIG.SECRET_FOO);
console.log('> secret value in obj', { secret: DMNO_CONFIG.SECRET_FOO });
console.log('> secret value in array', ['secret', DMNO_CONFIG.SECRET_FOO ]);

console.log('\nthe secret on the next line should not');
console.log('> secret value =', unredact(DMNO_CONFIG.SECRET_FOO));
// console.log('\nthe secret on the next line should not');
// console.log('> secret value =', unredact(DMNO_CONFIG.SECRET_FOO));

// https://astro.build/config
export default defineConfig({
Expand Down
14 changes: 7 additions & 7 deletions packages/core/src/config-engine/config-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ class CacheEntry {
this.encryptedValue = more?.encryptedValue;
}
async getEncryptedValue() {
return encrypt(CacheEntry.encryptionKey, this.value, CacheEntry.encryptionKeyName);
return encrypt(CacheEntry.encryptionKey, JSON.stringify(this.value), CacheEntry.encryptionKeyName);
}
// have to make this async because of the encryption call
async getJSON(): Promise<SerializedCacheEntry> {
Expand All @@ -283,9 +283,9 @@ class CacheEntry {
static async fromSerialized(itemKey: string, raw: SerializedCacheEntry) {
// currently this setup means the encryptedValue changes on every run...
// we could instead store the encryptedValue and reuse it if it has not changed
const value = await decrypt(CacheEntry.encryptionKey, raw.encryptedValue, CacheEntry.encryptionKeyName);
const valueStr = await decrypt(CacheEntry.encryptionKey, raw.encryptedValue, CacheEntry.encryptionKeyName);
// we are also tossing out the saved "usedBy" entries since we'll have new ones after this config run
return new CacheEntry(itemKey, value, {
return new CacheEntry(itemKey, JSON.parse(valueStr), {
updatedAt: new Date(raw.updatedAt),
encryptedValue: raw.encryptedValue,
});
Expand Down Expand Up @@ -628,7 +628,7 @@ export class DmnoWorkspace {
return this.valueCache[key].value;
}
}
async setCacheItem(key: string, value: string, usedBy?: string) {
async setCacheItem(key: string, value: any, usedBy?: string) {
if (this.cacheMode === 'skip') return undefined;
this.valueCache[key] = new CacheEntry(key, value, { usedBy });
}
Expand Down Expand Up @@ -952,9 +952,9 @@ export class ResolverContext {
async setCacheItem(key: string, value: ConfigValue) {
if (process.env.DISABLE_DMNO_CACHE) return;
if (value === undefined || value === null) return;
return this.service?.workspace.setCacheItem(key, value.toString(), this.itemFullPath);
return this.service?.workspace.setCacheItem(key, value, this.itemFullPath);
}
async getOrSetCacheItem(key: string, getValToWrite: () => Promise<string>) {
async getOrSetCacheItem(key: string, getValToWrite: () => Promise<ConfigValue>) {
if (!process.env.DISABLE_DMNO_CACHE) {
const cachedValue = await this.getCacheItem(key);
if (cachedValue) return cachedValue;
Expand Down Expand Up @@ -990,7 +990,7 @@ export abstract class DmnoConfigItemBase {

/** error encountered during resolution */
get resolutionError(): ResolutionError | undefined {
return this.valueResolver?.resolutionError;
return this.valueResolver?.selfOrChildResolutionError;
}

/** resolved value _after_ coercion logic applied */
Expand Down
25 changes: 17 additions & 8 deletions packages/core/src/config-engine/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,36 @@ export class DmnoError extends Error {

icon = '❌';

constructor(err: string | Error, readonly more?: {
tip?: string,
constructor(errOrMessage: string | Error, readonly more?: {
tip?: string | Array<string>,
err?: Error,
}) {
if (_.isError(err)) {
super(err.message);
this.originalError = err;
if (_.isError(errOrMessage)) {
super(errOrMessage.message);
this.originalError = errOrMessage;
this.icon = '💥';
} else {
super(err);
} else { // string
super(errOrMessage);
this.originalError = more?.err;
}
if (Array.isArray(more?.tip)) more.tip = more.tip.join('\n');
this.name = this.constructor.name;
}

get tip() {
if (!this.more?.tip) return undefined;
if (Array.isArray(this.more.tip)) return this.more.tip.join('\n');
return this.more.tip;
}

toJSON() {
return {
icon: this.icon,
type: this.type,
name: this.name,
message: this.message,
isUnexpected: this.isUnexpected,
...this.more,
...this.tip && { tip: this.tip },
};
}
}
Expand Down
27 changes: 14 additions & 13 deletions packages/core/src/config-engine/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,17 +215,12 @@ export abstract class DmnoPlugin<

initializedPluginInstanceNames.push(instanceName);

// const callStack = new Error('').stack!.split('\n');
// const pluginDefinitionPath = callStack[2]
// .replace(/.*\(/, '')
// .replace(/:.*\)/, '');
// // special case for local dev when we have the plugins symlinked by pnpm
// if (pluginDefinitionPath.includes('/core/packages/plugins/')) {
// const pluginPackageName
// } else {

// }
// console.log(pluginDefinitionPath);
// ideally we would detect the current package name and version automatically here but I dont think it's possible
// instead we made static properties, which really should be abstract, but that is not supported
// so here we have some runtime checks to ensure they have been set
// see https://github.com/microsoft/TypeScript/issues/34516
if (!this.pluginPackageName) throw new Error('DmnoPlugin class must set `static pluginPackageName` prop');
if (!this.pluginPackageVersion) throw new Error('DmnoPlugin class must set `static pluginPackageVersion` prop');
}

/** name of the plugin itself - which is the name of the class */
Expand All @@ -234,10 +229,16 @@ export abstract class DmnoPlugin<
icon?: string;

static cliPath?: string;
get cliPath() {
// these 2 should be required, but TS currently does not support static abstract
static pluginPackageName: string;
static pluginPackageVersion: string;
private getStaticProp(key: 'cliPath' | 'pluginPackageName' | 'pluginPackageVersion') {
const PluginClass = this.constructor as typeof DmnoPlugin;
return PluginClass.cliPath;
return PluginClass[key];
}
get cliPath() { return this.getStaticProp('cliPath'); }
get pluginPackageName() { return this.getStaticProp('pluginPackageName')!; }
get pluginPackageVersion() { return this.getStaticProp('pluginPackageVersion')!; }

/**
* reference back to the service this plugin was initialized in
Expand Down
Loading
Loading