summaryrefslogtreecommitdiff
path: root/worker
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2025-06-29 17:31:30 -0700
committerElizabeth Hunt <me@liz.coffee>2025-06-29 17:31:30 -0700
commit58be1809c46cbe517a18d86d0af52179dcc5cbf6 (patch)
tree9ccc678b3fd48c1a52fe501600dd2c2051740a55 /worker
parentd4791f3d357634daf506fb8f91cc5332a794c421 (diff)
downloadci-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.ts76
-rwxr-xr-xworker/scripts/ansible_playbook.ts4
-rwxr-xr-xworker/scripts/build_docker_image.ts49
-rwxr-xr-xworker/scripts/checkout_ci.ts72
-rw-r--r--worker/secret.ts73
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> --