From 9ee3bf3345b006a745b2ee28fee3613819011796 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sat, 26 Jul 2025 17:39:44 -0700 Subject: Adds session-level storage for bw cli --- .ci/ci.cjs | 4 +- .ci/ci.ts | 7 ++- u/trace/trace.ts | 14 ++++-- worker/executor.ts | 2 +- worker/scripts/ansible_playbook.ts | 4 +- worker/scripts/build_docker_image.ts | 9 ++-- worker/secret.ts | 83 ++++++++++++++++++------------------ 7 files changed, 65 insertions(+), 58 deletions(-) diff --git a/.ci/ci.cjs b/.ci/ci.cjs index 564ff20..f8a5e2b 100755 --- a/.ci/ci.cjs +++ b/.ci/ci.cjs @@ -578,7 +578,9 @@ var getPipeline = () => { dockerfile: `${_package}/Dockerfile` } })); - subPackages.forEach((job) => gitHookPipeline.addStage({ parallelJobs: [job] })); + gitHookPipeline.addStage({ + parallelJobs: subPackages + }); const isRelease = branch === "release"; if (!isRelease) { return gitHookPipeline.build(); diff --git a/.ci/ci.ts b/.ci/ci.ts index cd3cbb4..e6663db 100644 --- a/.ci/ci.ts +++ b/.ci/ci.ts @@ -50,10 +50,9 @@ const getPipeline = () => { }, }, ); - subPackages.forEach((job) => gitHookPipeline.addStage({ parallelJobs: [job] })); - // gitHookPipeline.addStage({ - // parallelJobs: subPackages, - // }); + gitHookPipeline.addStage({ + parallelJobs: subPackages, + }); const isRelease = branch === 'release'; if (!isRelease) { diff --git a/u/trace/trace.ts b/u/trace/trace.ts index e316ca8..bde83a3 100644 --- a/u/trace/trace.ts +++ b/u/trace/trace.ts @@ -39,13 +39,19 @@ export class LogMetricTrace implements ITrace { private metricsTrace: ITrace, ) {} + // public traceScope(trace: LogTraceSupplier | MetricsTraceSupplier): LogMetricTrace { + // if (isMetricsTraceSupplier(trace)) { + // this.metricsTrace = this.metricsTrace.traceScope(trace); + // return this; + // } + // this.logTrace = this.logTrace.traceScope(trace); + // return this; + // } public traceScope(trace: LogTraceSupplier | MetricsTraceSupplier): LogMetricTrace { if (isMetricsTraceSupplier(trace)) { - this.metricsTrace = this.metricsTrace.traceScope(trace); - return this; + return new LogMetricTrace(this.logTrace, this.metricsTrace.traceScope(trace)); } - this.logTrace = this.logTrace.traceScope(trace); - return this; + return new LogMetricTrace(this.logTrace.traceScope(trace), this.metricsTrace); } public trace(trace: LogTraceSupplier | MetricsTraceSupplier) { diff --git a/worker/executor.ts b/worker/executor.ts index 2640f39..3cd2cbc 100644 --- a/worker/executor.ts +++ b/worker/executor.ts @@ -44,7 +44,7 @@ export const executePipeline = ( .flatMap(TraceUtil.withFunctionTrace(executePipeline)) .flatMap(TraceUtil.withMetricTrace(pipelinesMetric)) .map(async (_tPipeline): Promise> => { - for (const [i, serialStage] of tPipeline.get().serialJobs.entries()) { + for (const [i, serialStage] of _tPipeline.get().serialJobs.entries()) { const tPipeline = _tPipeline.traceScope(() => `Stage = ${i}`); const parallelJobs = tPipeline .peek((t) => t.trace.trace(`do your best little stage :> ${JSON.stringify(serialStage)}`)) diff --git a/worker/scripts/ansible_playbook.ts b/worker/scripts/ansible_playbook.ts index 6eb4d47..20f85d8 100755 --- a/worker/scripts/ansible_playbook.ts +++ b/worker/scripts/ansible_playbook.ts @@ -36,12 +36,12 @@ await LogMetricTraceable.ofLogTraceable(_logJob) tEitherJob.get().flatMapAsync((job) => eitherVault.flatMapAsync(async (vault) => { const eitherKey = await vault.unlock(tEitherJob); + tEitherJob.trace.trace('unlocked vault :3'); return eitherKey.mapRight((key) => ({ job, key, vault })); }), ), ) .map(async (tEitherJobVault) => { - tEitherJobVault.trace.trace('getting ansible secwets uwu~'); const eitherJobVault = await tEitherJobVault.get(); const eitherSshKey = await eitherJobVault.flatMapAsync(({ key, vault }) => @@ -49,7 +49,7 @@ await LogMetricTraceable.ofLogTraceable(_logJob) ); const eitherSshKeyFile = await eitherSshKey.mapRight(({ notes }) => notes).flatMapAsync(saveToTempFile); const eitherAnsibleSecrets = await eitherJobVault.flatMapAsync(({ key, vault }) => - vault.fetchSecret(tEitherJobVault, key, 'ansible_playbooks'), + vault.fetchSecret(tEitherJobVault, key, 'ansible_secrets'), ); const eitherAnsibleSecretsFile = await eitherAnsibleSecrets .mapRight(({ notes }) => notes) diff --git a/worker/scripts/build_docker_image.ts b/worker/scripts/build_docker_image.ts index 6b4765c..759dfc1 100755 --- a/worker/scripts/build_docker_image.ts +++ b/worker/scripts/build_docker_image.ts @@ -58,9 +58,10 @@ await LogMetricTraceable.ofLogTraceable(_logJob) .map(async (tEitherJobVault) => { tEitherJobVault.trace.trace('logging into the wegistwy uwu~'); const eitherJobVault = await tEitherJobVault.get(); - const eitherDockerRegistryLoginItem = await eitherJobVault.flatMapAsync( - ({ job, key, vault }) => vault.fetchSecret(tEitherJobVault, key, job.arguments.registry), - // .finally(() => vault.lock(tEitherJobVault, key)), + const eitherDockerRegistryLoginItem = await eitherJobVault.flatMapAsync(({ job, key, vault }) => + vault + .fetchSecret(tEitherJobVault, key, job.arguments.registry) + .finally(() => vault.lock(tEitherJobVault, key)), ); return eitherDockerRegistryLoginItem.flatMapAsync(({ login }) => eitherJobVault.flatMapAsync(async ({ job }) => { @@ -83,7 +84,7 @@ await LogMetricTraceable.ofLogTraceable(_logJob) const eitherBuiltImage = await eitherWithAuthdRegistryBuildJob.flatMapAsync(({ arguments: args }) => tEitherWithAuthdRegistryBuildJob .move(getBuildCommand(args)) - .peek(t => t.trace.trace('finally building the image~ (◕ᴗ◕✿)')) + .peek((t) => t.trace.trace('finally building the image~ (◕ᴗ◕✿)')) .map((tBuildCmd) => getStdout(tBuildCmd, { env: {}, diff --git a/worker/secret.ts b/worker/secret.ts index 30316bd..11daf06 100644 --- a/worker/secret.ts +++ b/worker/secret.ts @@ -2,12 +2,16 @@ import { Either, getRequiredEnvVars, getStdout, + getStdoutMany, type IEither, type ITraceable, type LogMetricTraceSupplier, Metric, TraceUtil, } from '@emprespresso/pengueno'; +import { randomUUID } from 'node:crypto'; +import { mkdirSync } from 'node:fs'; +import path from 'node:path'; // -- -- export interface SecretItem { @@ -35,55 +39,51 @@ export interface IVault { // -- -- type TClient = ITraceable; -type TKey = string; +type TKey = { + BW_SESSION: string; + BITWARDENCLI_APPDATA_DIR: string; +}; type TItemId = string; export class Bitwarden implements IVault { constructor(private readonly config: BitwardenConfig) {} public unlock(client: TClient) { - const authed = client + return client .move(this.config) .flatMap(TraceUtil.withMetricTrace(Bitwarden.loginMetric)) - .flatMap((tConfig) => tConfig.move(`bw config server ${tConfig.get().server}`).map(getStdout)) - .map(async (tEitherWithConfig) => { - const eitherWithConfig = await tEitherWithConfig.get(); - return eitherWithConfig.flatMapAsync((_) => - tEitherWithConfig - .peek((t) => t.trace.trace('logging in~ ^.^')) - .move('bw login --apikey --quiet') - .map(getStdout) - .get(), - ); - }) - .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(Bitwarden.loginMetric))); - const unlocked = authed - .flatMap(TraceUtil.withMetricTrace(Bitwarden.unlockVaultMetric)) - .map(async (tEitherWithAuthd) => { - const eitherWithAuthd = await tEitherWithAuthd.get(); - return eitherWithAuthd.flatMapAsync((_) => - tEitherWithAuthd - .peek((t) => t.trace.trace('unlocking the secret vault~ (◕ᴗ◕✿)')) - .move('bw unlock --passwordenv BW_PASSWORD --raw') - .map(getStdout) - .get(), - ); - }) - .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(Bitwarden.unlockVaultMetric))); - return unlocked.get(); + .map((tConfig) => + Either.fromFailable }>( + () => { + const sessionPath = path.join(this.config.sessionBaseDirectory, randomUUID()); + mkdirSync(sessionPath, { recursive: true }); + return { config: tConfig.get(), key: { BITWARDENCLI_APPDATA_DIR: sessionPath } }; + }, + ), + ) + .map((tEitherConfig) => + tEitherConfig + .get() + .flatMapAsync(({ config: { server }, key }) => + getStdoutMany( + tEitherConfig.move([ + `bw config server ${server}`, + `bw login --apikey --quiet`, + `bw unlock --passwordenv BW_PASSWORD --raw`, + ]), + { env: key }, + ).then((res) => res.mapRight((out) => ({ ...key, BW_SESSION: out.at(-1)! }))), + ), + ) + .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(Bitwarden.loginMetric))) + .get(); } - public fetchSecret(client: TClient, key: string, item: string): Promise> { + public fetchSecret(client: TClient, key: TKey, item: string): Promise> { return client .move(key) .flatMap(TraceUtil.withMetricTrace(Bitwarden.fetchSecretMetric)) .peek((tSession) => tSession.trace.trace(`looking for your secret ${item} (⑅˘꒳˘)`)) - .flatMap((tSession) => - tSession.move('bw list items').map((listCmd) => - getStdout(listCmd, { - env: { BW_SESSION: tSession.get() }, - }), - ), - ) + .flatMap((tSession) => tSession.move('bw list items').map((listCmd) => getStdout(listCmd, { env: key }))) .map( TraceUtil.promiseify((tEitherItemsJson) => tEitherItemsJson @@ -110,11 +110,7 @@ export class Bitwarden implements IVault { .flatMap(TraceUtil.withMetricTrace(Bitwarden.lockVaultMetric)) .peek((tSession) => tSession.trace.trace(`taking care of locking the vault :3`)) .flatMap((tSession) => - tSession.move('bw lock && bw logout').map((lockCmd) => - getStdout(lockCmd, { - env: { BW_SESSION: tSession.get() }, - }), - ), + tSession.move('bw lock && bw logout').map((lockCmd) => getStdout(lockCmd, { env: key })), ) .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(Bitwarden.lockVaultMetric))) .peek( @@ -124,12 +120,14 @@ export class Bitwarden implements IVault { .mapRight(() => tEitherWithLocked.trace.trace('all locked up and secure now~ (。•̀ᴗ-)✧')), ), ) + .map(TraceUtil.promiseify((e) => e.get().mapRight(() => key))) .get(); } - public static getConfigFromEnvironment(): IEither { + public static getConfigFromEnvironment(sessionBaseDirectory = '/tmp/secret'): IEither { return getRequiredEnvVars(['BW_SERVER', 'BW_CLIENTSECRET', 'BW_CLIENTID', 'BW_PASSWORD']).mapRight( ({ BW_SERVER, BW_CLIENTSECRET, BW_CLIENTID }) => ({ + sessionBaseDirectory, clientId: BW_CLIENTID, secret: BW_CLIENTSECRET, server: BW_SERVER, @@ -144,6 +142,7 @@ export class Bitwarden implements IVault { } export interface BitwardenConfig { + sessionBaseDirectory: string; server: string; secret: string; clientId: string; -- cgit v1.2.3-70-g09d2