diff options
author | Elizabeth Hunt <me@liz.coffee> | 2025-06-29 17:31:30 -0700 |
---|---|---|
committer | Elizabeth Hunt <me@liz.coffee> | 2025-06-29 17:31:30 -0700 |
commit | 58be1809c46cbe517a18d86d0af52179dcc5cbf6 (patch) | |
tree | 9ccc678b3fd48c1a52fe501600dd2c2051740a55 /worker | |
parent | d4791f3d357634daf506fb8f91cc5332a794c421 (diff) | |
download | ci-58be1809c46cbe517a18d86d0af52179dcc5cbf6.tar.gz ci-58be1809c46cbe517a18d86d0af52179dcc5cbf6.zip |
Move to nodejs and also lots of significant refactoring that should've been broken up but idgaf
Diffstat (limited to 'worker')
-rw-r--r-- | worker/executor.ts | 76 | ||||
-rwxr-xr-x | worker/scripts/ansible_playbook.ts | 4 | ||||
-rwxr-xr-x | worker/scripts/build_docker_image.ts | 49 | ||||
-rwxr-xr-x | worker/scripts/checkout_ci.ts | 72 | ||||
-rw-r--r-- | worker/secret.ts | 73 |
5 files changed, 119 insertions, 155 deletions
diff --git a/worker/executor.ts b/worker/executor.ts index f4b7906..bfcbc37 100644 --- a/worker/executor.ts +++ b/worker/executor.ts @@ -13,74 +13,58 @@ import { import type { Job, JobArgT, Pipeline } from '@emprespresso/ci_model'; // -- <job.exectuor> -- -const jobTypeMetric = memoize((type: string) => Metric.fromName(`run.${type}`)); -export const executeJob = (tJob: ITraceable<Job, LogMetricTraceSupplier>) => - tJob - .bimap(TraceUtil.withMetricTrace(jobTypeMetric(tJob.get().type))) +const jobTypeMetric = memoize((type: string) => Metric.fromName(`run.${type}`).asResult()); +export const executeJob = (tJob: ITraceable<Job, LogMetricTraceSupplier>) => { + const metric = jobTypeMetric(tJob.get().type); + return tJob + .flatMap(TraceUtil.withMetricTrace(metric)) .peek((tJob) => tJob.trace.trace(`let's do this little job ok!! ${tJob.get()}`)) .map((tJob) => validateExecutionEntries(tJob.get().arguments) .mapLeft((badEntries) => { - tJob.trace.addTrace(LogLevel.ERROR).trace(badEntries.toString()); + tJob.trace.traceScope(LogLevel.ERROR).trace(badEntries.toString()); return new Error('invalid job arguments'); }) .flatMapAsync((args) => getStdout(tJob.move(tJob.get().type), { env: args })), ) - .peek( - TraceUtil.promiseify((q) => - q.trace.trace( - q.get().fold(({ isLeft }) => jobTypeMetric(tJob.get().type)[isLeft ? 'failure' : 'success']), - ), - ), - ) + .flatMapAsync(TraceUtil.promiseify(TraceUtil.traceResultingEither(metric))) .get(); +}; // -- </job.exectuor> -- // -- <pipeline.executor> -- -const pipelinesMetric = Metric.fromName('pipelines'); +const pipelinesMetric = Metric.fromName('pipelines').asResult(); export const executePipeline = ( tPipeline: ITraceable<Pipeline, LogMetricTraceSupplier>, baseEnv?: JobArgT, ): Promise<IEither<Error, void>> => tPipeline - .bimap(TraceUtil.withFunctionTrace(executePipeline)) - .bimap(TraceUtil.withMetricTrace(pipelinesMetric)) - .map(async (tJobs): Promise<IEither<Error, void>> => { - for (const [i, serialStage] of tJobs.get().serialJobs.entries()) { - tJobs.trace.trace(`executing stage ${i}. do your best little stage :>\n${serialStage}`); - const jobResults = await Promise.all( - serialStage.parallelJobs.map((job) => - tJobs - .bimap((_) => [job, `stage ${i}`]) - .map( - (tJob) => - <Job>{ - ...tJob.get(), - arguments: { - ...baseEnv, - ...tJob.get().arguments, - }, - }, - ) + .flatMap(TraceUtil.withFunctionTrace(executePipeline)) + .flatMap(TraceUtil.withMetricTrace(pipelinesMetric)) + .map(async (_tPipeline): Promise<IEither<Error, void>> => { + for (const [i, serialStage] of tPipeline.get().serialJobs.entries()) { + const tPipeline = _tPipeline.flatMap(TraceUtil.withTrace(`Stage = ${i}`)); + const parallelJobs = tPipeline + .peek((t) => t.trace.trace(`do your best little stage :> ${serialStage}`)) + .move(serialStage.parallelJobs) + .coExtend((jobs) => + jobs.get().map((job) => <Job>{ ...job, arguments: { ...baseEnv, ...job.arguments } }), + ) + .map((job) => { + const metric = jobTypeMetric(job.get().type); + return job + .flatMap(TraceUtil.withMetricTrace(metric)) .map(executeJob) - .peek( - TraceUtil.promiseify((tEitherJobOutput) => - tEitherJobOutput - .get() - .mapRight((stdout) => tEitherJobOutput.trace.addTrace('STDOUT').trace(stdout)), - ), - ) - .get(), - ), - ); - const failures = jobResults.filter((e) => e.fold(({ isLeft }) => isLeft)); + .flatMapAsync(TraceUtil.promiseify(TraceUtil.traceResultingEither(metric))); + }); + const results = await Promise.all(parallelJobs.map((job) => job.get())); + const failures = results.filter((e) => e.left); if (failures.length > 0) { - tJobs.trace.trace(pipelinesMetric.failure); return Either.left(new Error(failures.toString())); } } - tJobs.trace.trace(pipelinesMetric.success); - return Either.right(undefined); + return Either.right(<void>undefined); }) + .flatMapAsync(TraceUtil.promiseify(TraceUtil.traceResultingEither(pipelinesMetric))) .get(); // -- </pipeline.executor> -- diff --git a/worker/scripts/ansible_playbook.ts b/worker/scripts/ansible_playbook.ts index c6d8f2c..4a22984 100755 --- a/worker/scripts/ansible_playbook.ts +++ b/worker/scripts/ansible_playbook.ts @@ -28,9 +28,9 @@ const eitherJob = getRequiredEnvVars(['path', 'playbooks']).mapRight( const eitherVault = Bitwarden.getConfigFromEnvironment().mapRight((config) => new Bitwarden(config)); const playbookMetric = Metric.fromName('ansiblePlaybook.playbook'); -const _logJob = LogTraceable.of(eitherJob).bimap(TraceUtil.withTrace('ansible_playbook')); +const _logJob = LogTraceable.of(eitherJob).flatMap(TraceUtil.withTrace('ansible_playbook')); await LogMetricTraceable.ofLogTraceable(_logJob) - .bimap(TraceUtil.withMetricTrace(playbookMetric)) + .flatMap(TraceUtil.withMetricTrace(playbookMetric)) .peek((tEitherJob) => tEitherJob.trace.trace('starting ansible playbook job! (⑅˘꒳˘)')) .map((tEitherJob) => tEitherJob.get().flatMapAsync((job) => diff --git a/worker/scripts/build_docker_image.ts b/worker/scripts/build_docker_image.ts index 228dfcc..b35031a 100755 --- a/worker/scripts/build_docker_image.ts +++ b/worker/scripts/build_docker_image.ts @@ -3,7 +3,6 @@ import { getRequiredEnvVars, getStdout, - LogLevel, LogTraceable, LogMetricTraceable, Metric, @@ -29,17 +28,18 @@ const eitherJob = getRequiredEnvVars([ ); const eitherVault = Bitwarden.getConfigFromEnvironment().mapRight((config) => new Bitwarden(config)); -const buildImageMetric = Metric.fromName('dockerImage.build'); -const loginMetric = Metric.fromName('dockerRegistry.login'); -const _logJob = LogTraceable.of(eitherJob).bimap((tEitherJob) => { - const trace = - 'build_docker_image.' + - tEitherJob.get().fold(({ isRight, value }) => (isRight ? value.arguments.buildTarget : '')); - return [tEitherJob.get(), trace]; +const buildImageMetric = Metric.fromName('dockerImage.build').asResult(); +const loginMetric = Metric.fromName('dockerRegistry.login').asResult(); +const _logJob = LogTraceable.of(eitherJob).flatMap((tEitherJob) => { + const trace = tEitherJob.get().fold( + () => 'NO_BUILD_TARGET', + ({ arguments: { buildTarget } }) => buildTarget, + ); + return tEitherJob.traceScope(() => `build_docker_image.${trace}`); }); await LogMetricTraceable.ofLogTraceable(_logJob) - .bimap(TraceUtil.withMetricTrace(buildImageMetric)) - .bimap(TraceUtil.withMetricTrace(loginMetric)) + .flatMap(TraceUtil.withMetricTrace(buildImageMetric)) + .flatMap(TraceUtil.withMetricTrace(loginMetric)) .peek((tEitherJob) => tEitherJob.trace.trace('starting docker image build job! (⑅˘꒳˘)')) .map((tEitherJob) => tEitherJob.get().flatMapAsync((job) => @@ -68,12 +68,7 @@ await LogMetricTraceable.ofLogTraceable(_logJob) }), ); }) - .peek(async (tEitherWithAuthdRegistry) => { - const eitherWithAuthdRegistry = await tEitherWithAuthdRegistry.get(); - return tEitherWithAuthdRegistry.trace.trace( - eitherWithAuthdRegistry.fold(({ isLeft }) => loginMetric[isLeft ? 'failure' : 'success']), - ); - }) + .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(loginMetric))) .map(async (tEitherWithAuthdRegistryBuildJob) => { const eitherWithAuthdRegistryBuildJob = await tEitherWithAuthdRegistryBuildJob.get(); tEitherWithAuthdRegistryBuildJob.trace.trace('finally building the image~ (◕ᴗ◕✿)'); @@ -92,19 +87,15 @@ await LogMetricTraceable.ofLogTraceable(_logJob) eitherWithAuthdRegistryBuildJob.mapRight((job) => ({ buildOutput, job })), ); }) - .peek(async (tEitherWithBuiltImage) => { - const eitherWithBuiltImage = await tEitherWithBuiltImage.get(); - eitherWithBuiltImage.fold(({ isLeft, value }) => { - tEitherWithBuiltImage.trace.trace(buildImageMetric[isLeft ? 'failure' : 'success']); - if (isLeft) { - tEitherWithBuiltImage.trace - .addTrace(LogLevel.ERROR) - .trace(`oh nyoo we couldn't buiwd the img :(( ${value}`); - return; - } - tEitherWithBuiltImage.trace.addTrace('buildOutput').trace(value.buildOutput); - }); - }) + .flatMapAsync(TraceUtil.promiseify(TraceUtil.traceResultingEither(buildImageMetric))) + .peek( + TraceUtil.promiseify((tBuilt) => + tBuilt.get().fold( + (err) => tBuilt.trace.trace(`oh nyoo we couldn't buiwd the img :(( ${err}`), + (ok) => tBuilt.trace.traceScope('buildOutput').trace(ok.buildOutput), + ), + ), + ) .map(async (tEitherWithBuiltImage) => { const eitherWithBuiltImage = await tEitherWithBuiltImage.get(); return eitherWithBuiltImage diff --git a/worker/scripts/checkout_ci.ts b/worker/scripts/checkout_ci.ts index 8e4dcca..fb71a16 100755 --- a/worker/scripts/checkout_ci.ts +++ b/worker/scripts/checkout_ci.ts @@ -11,6 +11,7 @@ import { Metric, prependWith, TraceUtil, + IEither, } from '@emprespresso/pengueno'; import { mkdir, readFile, rm } from 'fs/promises'; import { join } from 'path'; @@ -29,11 +30,14 @@ const eitherJob = getRequiredEnvVars(['remote', 'refname', 'rev']).mapRight( }, }, ); +const afterJob = eitherJob.flatMapAsync((job) => + Either.fromFailableAsync(() => rm(getWorkingDirectoryForCiJob(job), { recursive: true })), +); const ciRunMetric = Metric.fromName('checkout_ci.run'); -const _logJob = LogTraceable.of(eitherJob).bimap(TraceUtil.withTrace(`checkout_ci.${run}`)); +const _logJob = LogTraceable.of(eitherJob).flatMap(TraceUtil.withTrace(`checkout_ci.${run}`)); await LogMetricTraceable.ofLogTraceable(_logJob) - .bimap(TraceUtil.withMetricTrace(ciRunMetric)) + .flatMap(TraceUtil.withMetricTrace(ciRunMetric)) .map((tEitherJob) => tEitherJob.get().flatMapAsync((ciJob) => { const wd = getWorkingDirectoryForCiJob(ciJob); @@ -53,29 +57,28 @@ await LogMetricTraceable.ofLogTraceable(_logJob) ); }), ) - .map((tEitherCiJob) => - tEitherCiJob.get().then((eitherCiJob) => - eitherCiJob.flatMapAsync<{ cmd: Command; job: CheckoutCiJob }>((ciJob) => - Either.fromFailableAsync<Error, string>(() => - readFile(join(getSrcDirectoryForCiJob(ciJob), CI_WORKFLOW_FILE), 'utf-8'), - ).then((eitherWorkflowJson) => - eitherWorkflowJson - .flatMap((json) => Either.fromFailable<Error, unknown>(JSON.parse(json))) - .flatMap((eitherWorkflowParse) => { - if (isCiWorkflow(eitherWorkflowParse)) { - return Either.right({ - cmd: getPipelineGenerationCommand(ciJob, eitherWorkflowParse.workflow), - job: ciJob, - }); - } - return Either.left( - new Error("couldn't find any valid ci configuration (。•́︿•̀。), that's okay~"), - ); - }), - ), + .map(async (tEitherCiJob) => { + const eitherCiJob = await tEitherCiJob.get(); + const repoCiFileContents = await eitherCiJob.flatMapAsync((ciJob) => + Either.fromFailableAsync<Error, string>(() => + readFile(join(getSrcDirectoryForCiJob(ciJob), CI_WORKFLOW_FILE), 'utf-8'), ), - ), - ) + ); + return repoCiFileContents + .flatMap((fileText) => Either.fromFailable<Error, unknown>(() => JSON.parse(fileText))) + .flatMap((json) => { + return eitherCiJob.flatMap((ciJob): IEither<Error, { cmd: Command; job: CheckoutCiJob }> => { + if (!isCiWorkflow(json)) { + const e = new Error("couldn't find any valid ci configuration (。•́︿•̀。), that's okay~"); + return Either.left(e); + } + return Either.right({ + cmd: getPipelineGenerationCommand(ciJob, json.workflow), + job: ciJob, + }); + }); + }); + }) .map(async (tEitherPipelineGenerationCommand) => { const eitherJobCommand = await tEitherPipelineGenerationCommand.get(); const eitherPipeline = await eitherJobCommand.flatMapAsync((jobCommand) => @@ -107,17 +110,14 @@ await LogMetricTraceable.ofLogTraceable(_logJob) .get(), ); }) - .get() - .then((e) => - e - .flatMap(() => eitherJob) - .fold(({ isLeft, isRight, value }) => { - if (isLeft || !isRight) throw value; - return rm(getWorkingDirectoryForCiJob(value), { - recursive: true, - }); - }), - ); + .map(async (tCompletePipeline) => { + const completePipeline = await tCompletePipeline.get(); + return completePipeline.fold( + (e) => Promise.reject(e), + () => afterJob, + ); + }) + .get(); const getWorkingDirectoryForCiJob = (job: CheckoutCiJob) => `${job.arguments.returnPath}/${job.arguments.run}`; @@ -130,7 +130,7 @@ const getPipelineGenerationCommand = ( pipelineGeneratorPath: string, image = _image, runFlags = _runFlags, -) => [ +): Command => [ 'docker', 'run', ...runFlags, diff --git a/worker/secret.ts b/worker/secret.ts index e3edb2d..e533b16 100644 --- a/worker/secret.ts +++ b/worker/secret.ts @@ -10,18 +10,21 @@ import { } from '@emprespresso/pengueno'; // -- <ISecret> -- -export interface LoginItem { +export interface SecretItem { + name: string; +} + +export interface LoginItem extends SecretItem { login: { username: string; password: string; }; } -export interface SecureNote { +export interface SecureNote extends SecretItem { notes: string; } -export type SecretItem = LoginItem | SecureNote; export interface IVault<TClient, TKey, TItemId> { unlock: (client: TClient) => Promise<IEither<Error, TKey>>; lock: (client: TClient, key: TKey) => Promise<IEither<Error, TKey>>; @@ -30,7 +33,7 @@ export interface IVault<TClient, TKey, TItemId> { } // -- </ISecret> -- -// -- <IVault> -- +// -- <Vault> -- type TClient = ITraceable<unknown, LogMetricTraceSupplier>; type TKey = string; type TItemId = string; @@ -38,9 +41,9 @@ export class Bitwarden implements IVault<TClient, TKey, TItemId> { constructor(private readonly config: BitwardenConfig) {} public unlock(client: TClient) { - return client + const authed = client .move(this.config) - .bimap(TraceUtil.withMetricTrace(Bitwarden.loginMetric)) + .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(); @@ -49,12 +52,9 @@ export class Bitwarden implements IVault<TClient, TKey, TItemId> { tEitherWithConfig.move('bw login --apikey --quiet').map(getStdout).get(), ); }) - .peek(async (tEitherWithAuthd) => { - const eitherWithAuthd = await tEitherWithAuthd.get(); - return tEitherWithAuthd.trace.trace( - eitherWithAuthd.fold(({ isLeft }) => Bitwarden.loginMetric[isLeft ? 'failure' : 'success']), - ); - }) + .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(Bitwarden.loginMetric))); + const unlocked = authed + .flatMap(TraceUtil.withMetricTrace(Bitwarden.unlockVaultMetric)) .map(async (tEitherWithAuthd) => { const eitherWithAuthd = await tEitherWithAuthd.get(); tEitherWithAuthd.trace.trace('unlocking the secret vault~ (◕ᴗ◕✿)'); @@ -62,19 +62,14 @@ export class Bitwarden implements IVault<TClient, TKey, TItemId> { tEitherWithAuthd.move('bw unlock --passwordenv BW_PASSWORD --raw').map(getStdout).get(), ); }) - .peek(async (tEitherWithSession) => { - const eitherWithAuthd = await tEitherWithSession.get(); - return tEitherWithSession.trace.trace( - eitherWithAuthd.fold(({ isLeft }) => Bitwarden.unlockVaultMetric[isLeft ? 'failure' : 'success']), - ); - }) - .get(); + .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(Bitwarden.unlockVaultMetric))); + return unlocked.get(); } public fetchSecret<T extends SecretItem>(client: TClient, key: string, item: string): Promise<IEither<Error, T>> { return client .move(key) - .bimap(TraceUtil.withMetricTrace(Bitwarden.fetchSecretMetric)) + .flatMap(TraceUtil.withMetricTrace(Bitwarden.fetchSecretMetric)) .peek((tSession) => tSession.trace.trace(`looking for your secret ${item} (⑅˘꒳˘)`)) .flatMap((tSession) => tSession.move('bw list items').map((listCmd) => @@ -88,8 +83,7 @@ export class Bitwarden implements IVault<TClient, TKey, TItemId> { tEitherItemsJson .get() .flatMap( - (itemsJson): IEither<Error, Array<T & { name: string }>> => - Either.fromFailable(() => JSON.parse(itemsJson)), + (itemsJson): IEither<Error, Array<T>> => Either.fromFailable(() => JSON.parse(itemsJson)), ) .flatMap((itemsList): IEither<Error, T> => { const secret = itemsList.find(({ name }) => name === item); @@ -100,19 +94,14 @@ export class Bitwarden implements IVault<TClient, TKey, TItemId> { }), ), ) - .peek(async (tEitherWithSecret) => { - const eitherWithSecret = await tEitherWithSecret.get(); - return tEitherWithSecret.trace.trace( - eitherWithSecret.fold(({ isLeft }) => Bitwarden.fetchSecretMetric[isLeft ? 'failure' : 'success']), - ); - }) + .flatMapAsync(TraceUtil.promiseify(TraceUtil.traceResultingEither(Bitwarden.fetchSecretMetric))) .get(); } public lock(client: TClient, key: TKey) { return client .move(key) - .bimap(TraceUtil.withMetricTrace(Bitwarden.lockVaultMetric)) + .flatMap(TraceUtil.withMetricTrace(Bitwarden.lockVaultMetric)) .peek((tSession) => tSession.trace.trace(`taking care of locking the vault :3`)) .flatMap((tSession) => tSession.move('bw lock').map((lockCmd) => @@ -121,14 +110,14 @@ export class Bitwarden implements IVault<TClient, TKey, TItemId> { }), ), ) - .peek(async (tEitherWithLocked) => { - const eitherWithLocked = await tEitherWithLocked.get(); - return eitherWithLocked.fold(({ isLeft }) => { - tEitherWithLocked.trace.trace(Bitwarden.lockVaultMetric[isLeft ? 'failure' : 'success']); - if (isLeft) return; - tEitherWithLocked.trace.trace('all locked up and secure now~ (。•̀ᴗ-)✧'); - }); - }) + .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(Bitwarden.lockVaultMetric))) + .peek( + TraceUtil.promiseify((tEitherWithLocked) => + tEitherWithLocked + .get() + .mapRight(() => tEitherWithLocked.trace.trace('all locked up and secure now~ (。•̀ᴗ-)✧')), + ), + ) .get(); } @@ -142,10 +131,10 @@ export class Bitwarden implements IVault<TClient, TKey, TItemId> { ); } - private static loginMetric = Metric.fromName('Bitwarden.login'); - private static unlockVaultMetric = Metric.fromName('Bitwarden.unlockVault'); - private static fetchSecretMetric = Metric.fromName('Bitwarden.fetchSecret'); - private static lockVaultMetric = Metric.fromName('Bitwarden.lock'); + private static loginMetric = Metric.fromName('Bitwarden.login').asResult(); + private static unlockVaultMetric = Metric.fromName('Bitwarden.unlockVault').asResult(); + private static fetchSecretMetric = Metric.fromName('Bitwarden.fetchSecret').asResult(); + private static lockVaultMetric = Metric.fromName('Bitwarden.lock').asResult(); } export interface BitwardenConfig { @@ -153,4 +142,4 @@ export interface BitwardenConfig { secret: string; clientId: string; } -// -- </IVault> -- +// -- </Vault> -- |