summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2025-07-26 17:39:44 -0700
committerElizabeth Hunt <me@liz.coffee>2025-07-26 18:09:32 -0700
commit9ee3bf3345b006a745b2ee28fee3613819011796 (patch)
tree5e92342d75d0a72102f320b85363cdbb36f3de43
parentdf1a7eec824d9e9d99c58b1f7792c8f384955273 (diff)
downloadci-9ee3bf3345b006a745b2ee28fee3613819011796.tar.gz
ci-9ee3bf3345b006a745b2ee28fee3613819011796.zip
Adds session-level storage for bw cli
-rwxr-xr-x.ci/ci.cjs4
-rw-r--r--.ci/ci.ts7
-rw-r--r--u/trace/trace.ts14
-rw-r--r--worker/executor.ts2
-rwxr-xr-xworker/scripts/ansible_playbook.ts4
-rwxr-xr-xworker/scripts/build_docker_image.ts9
-rw-r--r--worker/secret.ts83
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<LogMetricTraceSupplier> {
private metricsTrace: ITrace<MetricsTraceSupplier>,
) {}
+ // 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<IEither<Error, void>> => {
- 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<SecureNote>(tEitherJobVault, key, 'ansible_playbooks'),
+ vault.fetchSecret<SecureNote>(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<LoginItem>(tEitherJobVault, key, job.arguments.registry),
- // .finally(() => vault.lock(tEitherJobVault, key)),
+ const eitherDockerRegistryLoginItem = await eitherJobVault.flatMapAsync(({ job, key, vault }) =>
+ vault
+ .fetchSecret<LoginItem>(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';
// -- <ISecret> --
export interface SecretItem {
@@ -35,55 +39,51 @@ export interface IVault<TClient, TKey, TItemId> {
// -- <Vault> --
type TClient = ITraceable<unknown, LogMetricTraceSupplier>;
-type TKey = string;
+type TKey = {
+ BW_SESSION: string;
+ BITWARDENCLI_APPDATA_DIR: string;
+};
type TItemId = string;
export class Bitwarden implements IVault<TClient, TKey, TItemId> {
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<Error, { config: BitwardenConfig; key: Pick<TKey, 'BITWARDENCLI_APPDATA_DIR'> }>(
+ () => {
+ 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<T extends SecretItem>(client: TClient, key: string, item: string): Promise<IEither<Error, T>> {
+ public fetchSecret<T extends SecretItem>(client: TClient, key: TKey, item: string): Promise<IEither<Error, T>> {
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<TClient, TKey, TItemId> {
.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<TClient, TKey, TItemId> {
.mapRight(() => tEitherWithLocked.trace.trace('all locked up and secure now~ (。•̀ᴗ-)✧')),
),
)
+ .map(TraceUtil.promiseify((e) => e.get().mapRight(() => key)))
.get();
}
- public static getConfigFromEnvironment(): IEither<Error, BitwardenConfig> {
+ public static getConfigFromEnvironment(sessionBaseDirectory = '/tmp/secret'): IEither<Error, BitwardenConfig> {
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<TClient, TKey, TItemId> {
}
export interface BitwardenConfig {
+ sessionBaseDirectory: string;
server: string;
secret: string;
clientId: string;