diff options
author | Elizabeth <me@liz.coffee> | 2025-06-02 18:07:31 -0700 |
---|---|---|
committer | Elizabeth <me@liz.coffee> | 2025-06-02 18:07:31 -0700 |
commit | d9c418425b035f00c950e6a83df9470d7af80413 (patch) | |
tree | 07c7445d7800a6ac6a3cf6b404ebcf4e27aad245 | |
parent | 98f5c21aa65bbbca01a186a754249335b4afef57 (diff) | |
download | ci-d9c418425b035f00c950e6a83df9470d7af80413.tar.gz ci-d9c418425b035f00c950e6a83df9470d7af80413.zip |
Use log traceable in metric traceable
-rw-r--r-- | .zed/settings.json | 1 | ||||
-rwxr-xr-x | mod.ts | 47 | ||||
-rw-r--r-- | server/job.ts | 316 | ||||
-rw-r--r-- | server/mod.ts | 10 | ||||
-rw-r--r-- | u/fn/either.ts | 188 | ||||
-rw-r--r-- | u/process/argv.ts | 10 | ||||
-rw-r--r-- | u/server/activity/fourohfour.ts | 9 | ||||
-rw-r--r-- | u/server/request.ts | 11 | ||||
-rw-r--r-- | u/server/response.ts | 10 | ||||
-rw-r--r-- | u/trace/itrace.ts | 46 | ||||
-rw-r--r-- | u/trace/logger.ts | 4 | ||||
-rw-r--r-- | u/trace/metrics.ts | 4 | ||||
-rw-r--r-- | u/trace/trace.ts | 32 | ||||
-rw-r--r-- | worker/executor.ts | 4 | ||||
-rw-r--r-- | worker/jobs/ci_pipeline.run | 5 | ||||
-rwxr-xr-x | worker/scripts/ansible_playbook | 6 | ||||
-rwxr-xr-x | worker/scripts/build_docker_image | 7 | ||||
-rw-r--r-- | worker/secret.ts | 344 |
18 files changed, 515 insertions, 539 deletions
diff --git a/.zed/settings.json b/.zed/settings.json index cf1756f..78b2d5d 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -1,4 +1,5 @@ { + "formatter": "prettier", "lsp": { "deno": { "settings": { @@ -1,31 +1,36 @@ #!/usr/bin/env -S deno run --allow-env --allow-net -import { argv } from "@emprespresso/pengueno"; +import { argv, IEither, Either } from "@emprespresso/pengueno"; import { runServer } from "@emprespresso/ci_server"; -const main = (_argv = Deno.args) => { - const defaults = { - "--port": "9000", - "--host": "0.0.0.0", - }; - const _args = argv(["--run-server", "--port", "--host"], defaults, _argv); - const args = _args.fold((err, args) => { - if (!args || err) throw err; - return { +const _defaults = { + "--port": "9000", + "--host": "0.0.0.0", +}; +const main = ( + _argv = Deno.args, + defaults = _defaults, +): Promise<IEither<Error, void>> => + argv(["--run-server", "--port", "--host"], defaults, _argv) + .mapRight((args) => ({ server_mode: "--run-server" in args, port: parseInt(args["--port"]), host: args["--host"], - }; - }); - - const promises: Array<Promise<void>> = []; - if (args.server_mode) { - promises.push(runServer(args.port, args.host)); - } - - return Promise.all(promises); -}; + })) + .flatMapAsync((runConfig) => { + if (runConfig.server_mode) { + return runServer(runConfig.port, runConfig.host); + } + return Promise.resolve(Either.right(undefined)); + }); if (import.meta.main) { - await main(); + await main().then((eitherDone) => + eitherDone.fold(({ isLeft, value }) => { + if (!isLeft) return; + + console.error(value); + Deno.exit(1); + }), + ); } diff --git a/server/job.ts b/server/job.ts index 4e12b45..af11ac0 100644 --- a/server/job.ts +++ b/server/job.ts @@ -1,21 +1,21 @@ import { - getStdout, - type Mapper, - memoize, - Either, - ErrorSource, - type IActivity, - type IEither, - type ITraceable, - jsonModel, - JsonResponse, - LogLevel, - Metric, - PenguenoError, - type PenguenoRequest, - type ServerTrace, - TraceUtil, - validateExecutionEntries, + getStdout, + type Mapper, + memoize, + Either, + ErrorSource, + type IActivity, + type IEither, + type ITraceable, + jsonModel, + JsonResponse, + LogLevel, + Metric, + PenguenoError, + type PenguenoRequest, + type ServerTrace, + TraceUtil, + validateExecutionEntries, } from "@emprespresso/pengueno"; import { isJob, type Job } from "@emprespresso/ci_model"; @@ -23,98 +23,88 @@ import { isJob, type Job } from "@emprespresso/ci_model"; const wellFormedJobMetric = Metric.fromName("Job.WellFormed"); const jobJsonTransformer = ( - j: ITraceable<unknown, ServerTrace>, + j: ITraceable<unknown, ServerTrace>, ): IEither<PenguenoError, Job> => - j - .bimap(TraceUtil.withMetricTrace(wellFormedJobMetric)) - .map((tJson) => { - if (!isJob(tJson) || !validateExecutionEntries(tJson)) { - const err = "seems like a pwetty mawfomed job \\(-.-)/"; - tJson.trace.addTrace(LogLevel.WARN).trace(err); - return Either.left<PenguenoError, Job>( - new PenguenoError(err, 400), - ); - } - return Either.right<PenguenoError, Job>(tJson); - }) - .peek((tJob) => - tJob.trace.trace( - tJob - .get() - .fold(({ isLeft }) => - isLeft - ? wellFormedJobMetric.failure - : wellFormedJobMetric.success, - ), - ), - ) - .get(); + j + .bimap(TraceUtil.withMetricTrace(wellFormedJobMetric)) + .map((tJson) => { + if (!isJob(tJson) || !validateExecutionEntries(tJson)) { + const err = "seems like a pwetty mawfomed job \\(-.-)/"; + tJson.trace.addTrace(LogLevel.WARN).trace(err); + return Either.left<PenguenoError, Job>(new PenguenoError(err, 400)); + } + return Either.right<PenguenoError, Job>(tJson); + }) + .peek((tJob) => + tJob.trace.trace( + tJob + .get() + .fold(({ isLeft }) => + isLeft ? wellFormedJobMetric.failure : wellFormedJobMetric.success, + ), + ), + ) + .get(); export interface IJobHookActivity { - processHook: IActivity; + processHook: IActivity; } const jobHookRequestMetric = Metric.fromName("JobHook.process"); export class JobHookActivityImpl implements IJobHookActivity { - constructor( - private readonly queuer: IJobQueuer<ITraceable<Job, ServerTrace>>, - ) {} + constructor( + private readonly queuer: IJobQueuer<ITraceable<Job, ServerTrace>>, + ) {} - private trace(r: ITraceable<PenguenoRequest, ServerTrace>) { - return r - .bimap(TraceUtil.withClassTrace(this)) - .bimap(TraceUtil.withMetricTrace(jobHookRequestMetric)); - } + private trace(r: ITraceable<PenguenoRequest, ServerTrace>) { + return r + .bimap(TraceUtil.withClassTrace(this)) + .bimap(TraceUtil.withMetricTrace(jobHookRequestMetric)); + } - public processHook(r: ITraceable<PenguenoRequest, ServerTrace>) { - return this.trace(r) - .map(jsonModel(jobJsonTransformer)) - .map(async (tEitherJobJson) => { - const eitherJob = await tEitherJobJson.get(); - return eitherJob.flatMapAsync(async (job) => { - const eitherQueued = await tEitherJobJson - .move(job) - .map(this.queuer.queue) - .get(); - return eitherQueued.mapLeft( - (e) => new PenguenoError(e.message, 500), - ); - }); - }) - .peek( - TraceUtil.promiseify((tJob) => - tJob.get().fold(({ isRight, value }) => { - if (isRight) { - tJob.trace.trace(jobHookRequestMetric.success); - tJob.trace.trace( - `all queued up and weady to go :D !! ${value}`, - ); - return; - } - - tJob.trace.trace( - value.source === ErrorSource.SYSTEM - ? jobHookRequestMetric.failure - : jobHookRequestMetric.warn, - ); - tJob.trace.addTrace(value.source).trace(`${value}`); - }), - ), - ) - .map( - TraceUtil.promiseify( - (tEitherQueuedJob) => - new JsonResponse(r, tEitherQueuedJob.get(), { - status: tEitherQueuedJob - .get() - .fold(({ isRight, value }) => - isRight ? 200 : value.status, - ), - }), - ), - ) + public processHook(r: ITraceable<PenguenoRequest, ServerTrace>) { + return this.trace(r) + .map(jsonModel(jobJsonTransformer)) + .map(async (tEitherJobJson) => { + const eitherJob = await tEitherJobJson.get(); + return eitherJob.flatMapAsync(async (job) => { + const eitherQueued = await tEitherJobJson + .move(job) + .map(this.queuer.queue) .get(); - } + return eitherQueued.mapLeft((e) => new PenguenoError(e.message, 500)); + }); + }) + .peek( + TraceUtil.promiseify((tJob) => + tJob.get().fold(({ isRight, value }) => { + if (isRight) { + tJob.trace.trace(jobHookRequestMetric.success); + tJob.trace.trace(`all queued up and weady to go :D !! ${value}`); + return; + } + + tJob.trace.trace( + value.source === ErrorSource.SYSTEM + ? jobHookRequestMetric.failure + : jobHookRequestMetric.warn, + ); + tJob.trace.addTrace(value.source).trace(`${value}`); + }), + ), + ) + .map( + TraceUtil.promiseify( + (tEitherQueuedJob) => + new JsonResponse(r, tEitherQueuedJob.get(), { + status: tEitherQueuedJob + .get() + .fold(({ isRight, value }) => (isRight ? 200 : value.status)), + }), + ), + ) + .get(); + } } // -- </job.hook> -- @@ -123,82 +113,72 @@ export class JobHookActivityImpl implements IJobHookActivity { type QueuePosition = string; export class QueueError extends Error {} export interface IJobQueuer<TJob> { - queue: Mapper<TJob, Promise<IEither<QueueError, QueuePosition>>>; + queue: Mapper<TJob, Promise<IEither<QueueError, QueuePosition>>>; } export class LaminarJobQueuer - implements IJobQueuer<ITraceable<Job, ServerTrace>> + implements IJobQueuer<ITraceable<Job, ServerTrace>> { - constructor(private readonly queuePositionPrefix: string) {} + constructor(private readonly queuePositionPrefix: string) {} - private static GetJobTypeTrace = (jobType: string) => - `LaminarJobQueue.Queue.${jobType}`; - private static JobTypeMetrics = memoize((jobType: string) => - Metric.fromName(LaminarJobQueuer.GetJobTypeTrace(jobType)), - ); + private static GetJobTypeTrace = (jobType: string) => + `LaminarJobQueue.Queue.${jobType}`; + private static JobTypeMetrics = memoize((jobType: string) => + Metric.fromName(LaminarJobQueuer.GetJobTypeTrace(jobType)), + ); - public queue(j: ITraceable<Job, ServerTrace>) { - const { type: jobType } = j.get(); - const trace = LaminarJobQueuer.GetJobTypeTrace(jobType); - const metric = LaminarJobQueuer.JobTypeMetrics(trace); + public queue(j: ITraceable<Job, ServerTrace>) { + const { type: jobType } = j.get(); + const trace = LaminarJobQueuer.GetJobTypeTrace(jobType); + const metric = LaminarJobQueuer.JobTypeMetrics(trace); - return j - .bimap(TraceUtil.withTrace(trace)) - .bimap(TraceUtil.withMetricTrace(metric)) - .map((j) => { - const { type: jobType, arguments: args } = j.get(); - const laminarCommand = [ - "laminarc", - "queue", - jobType, - ...Object.entries(args).map( - ([key, val]) => `"${key}"="${val}"`, - ), - ]; - return laminarCommand; - }) - .peek((c) => - c.trace.trace( - `im so excited to see how this queue job will end!! (>ᴗ<): ${c - .get() - .toString()}`, - ), - ) - .map(getStdout) - .peek( - TraceUtil.promiseify((q) => - q.trace.trace( - q - .get() - .fold(({ isLeft }) => - isLeft ? metric.failure : metric.success, - ), - ), - ), - ) - .map( - TraceUtil.promiseify((q) => - q.get().fold(({ isLeft, value }) => { - if (isLeft) { - q.trace - .addTrace(LogLevel.ERROR) - .trace(value.toString()); - return Either.left<Error, string>(value); - } - q.trace - .addTrace(LogLevel.DEBUG) - .trace(`stdout ${value}`); - const [jobName, jobId] = value.split(":"); - const jobUrl = `${this.queuePositionPrefix}/jobs/${jobName}/${jobId}`; + return j + .bimap(TraceUtil.withTrace(trace)) + .bimap(TraceUtil.withMetricTrace(metric)) + .map((j) => { + const { type: jobType, arguments: args } = j.get(); + const laminarCommand = [ + "laminarc", + "queue", + jobType, + ...Object.entries(args).map(([key, val]) => `"${key}"="${val}"`), + ]; + return laminarCommand; + }) + .peek((c) => + c.trace.trace( + `im so excited to see how this queue job will end!! (>ᴗ<): ${c + .get() + .toString()}`, + ), + ) + .map(getStdout) + .peek( + TraceUtil.promiseify((q) => + q.trace.trace( + q + .get() + .fold(({ isLeft }) => (isLeft ? metric.failure : metric.success)), + ), + ), + ) + .map( + TraceUtil.promiseify((q) => + q.get().fold(({ isLeft, value }) => { + if (isLeft) { + q.trace.addTrace(LogLevel.ERROR).trace(value.toString()); + return Either.left<Error, string>(value); + } + q.trace.addTrace(LogLevel.DEBUG).trace(`stdout ${value}`); + const [jobName, jobId] = value.split(":"); + const jobUrl = `${this.queuePositionPrefix}/jobs/${jobName}/${jobId}`; - q.trace.trace( - `all queued up and weady to go~ (˘ω˘) => ${jobUrl}`, - ); - return Either.right<Error, string>(jobUrl); - }), - ), - ) - .get(); - } + q.trace.trace(`all queued up and weady to go~ (˘ω˘) => ${jobUrl}`); + return Either.right<Error, string>(jobUrl); + }), + ), + ) + .get(); + } } // -- </job.queuer> -- diff --git a/server/mod.ts b/server/mod.ts index 9dc57aa..1fd4e99 100644 --- a/server/mod.ts +++ b/server/mod.ts @@ -5,12 +5,18 @@ export * from "./health.ts"; export * from "./job.ts"; import { CiHookServer } from "./mod.ts"; +import { Either, type IEither } from "@emprespresso/pengueno"; const server = new CiHookServer(); -export const runServer = (port: number, host: string) => { +export const runServer = ( + port: number, + host: string, +): Promise<IEither<Error, void>> => { const serverConfig = { host, port, }; - return Deno.serve(serverConfig, (req) => server.serve(req)).finished; + return Either.fromFailable<Error, Deno.HttpServer>(() => + Deno.serve(serverConfig, (req) => server.serve(req)), + ).flatMapAsync((server) => Either.fromFailableAsync(server.finished)); }; diff --git a/u/fn/either.ts b/u/fn/either.ts index 124557c..bf90f16 100644 --- a/u/fn/either.ts +++ b/u/fn/either.ts @@ -4,115 +4,111 @@ type IEitherTag = "IEither"; const iEitherTag: IEitherTag = "IEither"; export interface _Either<LeftT, RightT, T> { - readonly isLeft: LeftT; - readonly isRight: RightT; - readonly value: T; + readonly isLeft: LeftT; + readonly isRight: RightT; + readonly value: T; } export type Left<E> = _Either<true, false, E>; export type Right<T> = _Either<false, true, T>; export interface IEither<E, T> { - readonly _tag: IEitherTag; - - mapBoth: <_E, _T>( - errBranch: Mapper<E, _E>, - okBranch: Mapper<T, _T>, - ) => IEither<_E, _T>; - fold: <_T>(folder: Mapper<Left<E> | Right<T>, _T>) => _T; - moveRight: <_T>(t: _T) => IEither<E, _T>; - mapRight: <_T>(mapper: Mapper<T, _T>) => IEither<E, _T>; - mapLeft: <_E>(mapper: Mapper<E, _E>) => IEither<_E, T>; - flatMap: <_T>(mapper: Mapper<T, IEither<E, _T>>) => IEither<E, _T>; - flatMapAsync: <_T>( - mapper: Mapper<T, Promise<IEither<E, _T>>>, - ) => Promise<IEither<E, _T>>; + readonly _tag: IEitherTag; + + mapBoth: <_E, _T>( + errBranch: Mapper<E, _E>, + okBranch: Mapper<T, _T>, + ) => IEither<_E, _T>; + fold: <_T>(folder: Mapper<Left<E> | Right<T>, _T>) => _T; + moveRight: <_T>(t: _T) => IEither<E, _T>; + mapRight: <_T>(mapper: Mapper<T, _T>) => IEither<E, _T>; + mapLeft: <_E>(mapper: Mapper<E, _E>) => IEither<_E, T>; + flatMap: <_T>(mapper: Mapper<T, IEither<E, _T>>) => IEither<E, _T>; + flatMapAsync: <_T>( + mapper: Mapper<T, Promise<IEither<E, _T>>>, + ) => Promise<IEither<E, _T>>; } export class Either<E, T> implements IEither<E, T> { - private readonly self: Left<E> | Right<T>; - - private constructor( - err?: E, - ok?: T, - public readonly _tag: IEitherTag = iEitherTag, - ) { - this.self = <Left<E> | Right<T>>{ - isLeft: typeof err !== "undefined", - isRight: typeof ok !== "undefined", - value: typeof err !== "undefined" ? err : ok!, - }; + private readonly self: Left<E> | Right<T>; + + private constructor( + err?: E, + ok?: T, + public readonly _tag: IEitherTag = iEitherTag, + ) { + this.self = <Left<E> | Right<T>>{ + isLeft: typeof err !== "undefined", + isRight: typeof ok !== "undefined", + value: typeof err !== "undefined" ? err : ok!, + }; + } + + public moveRight<_T>(t: _T) { + return this.mapRight(() => t); + } + + public fold<_T>(folder: Mapper<Left<E> | Right<T>, _T>): _T { + return folder(this.self); + } + + public mapBoth<_E, _T>( + errBranch: Mapper<E, _E>, + okBranch: Mapper<T, _T>, + ): IEither<_E, _T> { + if (this.self.isLeft) return Either.left(errBranch(this.self.value)); + return Either.right(okBranch(this.self.value)); + } + + public flatMap<_T>(mapper: Mapper<T, IEither<E, _T>>): IEither<E, _T> { + if (this.self.isRight) return mapper(this.self.value); + return Either.left<E, _T>(this.self.value); + } + + public mapRight<_T>(mapper: Mapper<T, _T>): IEither<E, _T> { + if (this.self.isRight) return Either.right<E, _T>(mapper(this.self.value)); + return Either.left<E, _T>(this.self.value); + } + + public mapLeft<_E>(mapper: Mapper<E, _E>): IEither<_E, T> { + if (this.self.isLeft) return Either.left<_E, T>(mapper(this.self.value)); + return Either.right<_E, T>(this.self.value); + } + + public async flatMapAsync<_T>( + mapper: Mapper<T, Promise<IEither<E, _T>>>, + ): Promise<IEither<E, _T>> { + if (this.self.isLeft) { + return Promise.resolve(Either.left<E, _T>(this.self.value)); } - - public moveRight<_T>(t: _T) { - return this.mapRight(() => t); - } - - public fold<_T>(folder: Mapper<Left<E> | Right<T>, _T>): _T { - return folder(this.self); - } - - public mapBoth<_E, _T>( - errBranch: Mapper<E, _E>, - okBranch: Mapper<T, _T>, - ): IEither<_E, _T> { - if (this.self.isLeft) return Either.left(errBranch(this.self.value)); - return Either.right(okBranch(this.self.value)); - } - - public flatMap<_T>(mapper: Mapper<T, IEither<E, _T>>): IEither<E, _T> { - if (this.self.isRight) return mapper(this.self.value); - return Either.left<E, _T>(this.self.value); - } - - public mapRight<_T>(mapper: Mapper<T, _T>): IEither<E, _T> { - if (this.self.isRight) - return Either.right<E, _T>(mapper(this.self.value)); - return Either.left<E, _T>(this.self.value); - } - - public mapLeft<_E>(mapper: Mapper<E, _E>): IEither<_E, T> { - if (this.self.isLeft) - return Either.left<_E, T>(mapper(this.self.value)); - return Either.right<_E, T>(this.self.value); - } - - public async flatMapAsync<_T>( - mapper: Mapper<T, Promise<IEither<E, _T>>>, - ): Promise<IEither<E, _T>> { - if (this.self.isLeft) { - return Promise.resolve(Either.left<E, _T>(this.self.value)); - } - return await mapper(this.self.value).catch((err) => - Either.left<E, _T>(err), - ); - } - - static left<E, T>(e: E): IEither<E, T> { - return new Either<E, T>(e, undefined); - } - static right<E, T>(t: T): IEither<E, T> { - return new Either<E, T>(undefined, t); - } - - static fromFailable<E, T>(s: Supplier<T>): IEither<E, T> { - try { - return Either.right<E, T>(s()); - } catch (e) { - return Either.left<E, T>(e as E); - } + return await mapper(this.self.value).catch((err) => + Either.left<E, _T>(err), + ); + } + + static left<E, T>(e: E): IEither<E, T> { + return new Either<E, T>(e, undefined); + } + static right<E, T>(t: T): IEither<E, T> { + return new Either<E, T>(undefined, t); + } + + static fromFailable<E, T>(s: Supplier<T>): IEither<E, T> { + try { + return Either.right<E, T>(s()); + } catch (e) { + return Either.left<E, T>(e as E); } + } - static async fromFailableAsync<E, T>( - s: Promise<T>, - ): Promise<IEither<E, T>> { - try { - return Either.right<E, T>(await s); - } catch (e) { - return Either.left<E, T>(e as E); - } + static async fromFailableAsync<E, T>(s: Promise<T>): Promise<IEither<E, T>> { + try { + return Either.right<E, T>(await s); + } catch (e) { + return Either.left<E, T>(e as E); } + } } export const isEither = <E, T>(o: unknown): o is IEither<E, T> => { - return isObject(o) && "_tag" in o && o._tag === "IEither"; + return isObject(o) && "_tag" in o && o._tag === "IEither"; }; diff --git a/u/process/argv.ts b/u/process/argv.ts index 7190531..8e85477 100644 --- a/u/process/argv.ts +++ b/u/process/argv.ts @@ -37,11 +37,11 @@ export const argv = <K extends string, V extends string>( .map((arg) => [arg, getArg(arg, argv)] as [K, IEither<Error, V>]) .map(([arg, specified]): [K, IEither<Error, V>] => [ arg, - specified.fold(({ isLeft, isRight, value}): IEither<Error, V> => { - if (isRight) { - return Either.right(value); - } - const hasDefaultVal = isLeft && defaultArgs && arg in defaultArgs; + specified.fold(({ isLeft, isRight, value }): IEither<Error, V> => { + if (isRight) { + return Either.right(value); + } + const hasDefaultVal = isLeft && defaultArgs && arg in defaultArgs; if (hasDefaultVal) { return Either.right(defaultArgs[arg]!); } diff --git a/u/server/activity/fourohfour.ts b/u/server/activity/fourohfour.ts index ed8c7eb..33cfe5f 100644 --- a/u/server/activity/fourohfour.ts +++ b/u/server/activity/fourohfour.ts @@ -7,12 +7,13 @@ import { } from "@emprespresso/pengueno"; const messages = [ - "D: Meow-t found! Your API call ran away!", - "404-bidden! But like...in a cute way >:3 !", - ":o Your data went on a paw-sible vacation!", + "D: meow-t found! your api call ran away!", + "404-bidden! but like...in a cute way >:3 !", + ":< your data went on a paw-sible vacation!", "uwu~ not found, but found our hearts instead!", ]; -const randomFourOhFour = () => messages[Math.random() * messages.length]; +const randomFourOhFour = () => + messages[Math.floor(Math.random() * messages.length)]; export interface IFourOhFourActivity { fourOhFour: IActivity; diff --git a/u/server/request.ts b/u/server/request.ts index c857f88..5dd9d7b 100644 --- a/u/server/request.ts +++ b/u/server/request.ts @@ -1,4 +1,5 @@ -import { LogMetricTraceable } from "@emprespresso/pengueno"; +import { LogMetricTraceable, LogTraceable } from "@emprespresso/pengueno"; +import { TraceUtil } from "../trace/util.ts"; const greetings = [ "hewwo :D", @@ -39,9 +40,11 @@ export class PenguenoRequest extends Request { const id = crypto.randomUUID(); const url = new URL(request.url); const { pathname } = url; - const traceSupplier = () => `[${id} <- ${request.method}'d @ ${pathname}]`; - return LogMetricTraceable.from( + const logTraceable = LogTraceable.of( new PenguenoRequest(url, { ...request }, id, new Date()), - ).bimap((_request) => [_request.get(), traceSupplier]); + ).bimap( + TraceUtil.withTrace(`Id=${id} Method=${request.method} Path=${pathname}`), + ); + return LogMetricTraceable.ofLogTraceable(logTraceable); } } diff --git a/u/server/response.ts b/u/server/response.ts index 4531157..629dbb5 100644 --- a/u/server/response.ts +++ b/u/server/response.ts @@ -28,12 +28,12 @@ const getResponse = ( }; }; -const ResponseCodeMetrics = [1, 2, 3, 4, 5].map((x) => +const ResponseCodeMetrics = [0, 1, 2, 3, 4, 5].map((x) => Metric.fromName(`response.${x}xx`), ); export const getResponseMetric = (status: number) => { - const index = Math.floor(status / 100) + 1; - return ResponseCodeMetrics[index] ?? ResponseCodeMetrics[5 - 1]; + const index = Math.floor(status / 100); + return ResponseCodeMetrics[index] ?? ResponseCodeMetrics[5]; }; export class PenguenoResponse extends Response { @@ -67,7 +67,9 @@ export class JsonResponse extends PenguenoResponse { super( req, JSON.stringify( - e.fold(({ isLeft, value }) => (isLeft ? { error: value } : { ok: value })), + e.fold(({ isLeft, value }) => + isLeft ? { error: value } : { ok: value }, + ), ), optsWithJsonContentType, ); diff --git a/u/trace/itrace.ts b/u/trace/itrace.ts index ed707c5..35164b5 100644 --- a/u/trace/itrace.ts +++ b/u/trace/itrace.ts @@ -9,29 +9,29 @@ export interface ITrace<TraceWith> { } export type ITraceableTuple<T, TraceWith> = [T, BaseTraceWith | TraceWith]; -export type ITraceableMapper<T, U, TraceWith, W = ITraceable<T, TraceWith>> = ( +export type ITraceableMapper<T, _T, TraceWith, W = ITraceable<T, TraceWith>> = ( w: W, -) => U; +) => _T; export interface ITraceable<T, Trace = BaseTraceWith> { readonly trace: ITrace<Trace>; get: Supplier<T>; - move: <U>(u: U) => ITraceable<U, Trace>; - map: <U>(mapper: ITraceableMapper<T, U, Trace>) => ITraceable<U, Trace>; - bimap: <U>( + move: <_T>(u: _T) => ITraceable<_T, Trace>; + map: <_T>(mapper: ITraceableMapper<T, _T, Trace>) => ITraceable<_T, Trace>; + bimap: <_T>( mapper: ITraceableMapper< T, - ITraceableTuple<U, Array<Trace> | Trace>, + ITraceableTuple<_T, Array<Trace> | Trace>, Trace >, - ) => ITraceable<U, Trace>; + ) => ITraceable<_T, Trace>; peek: (peek: ITraceableMapper<T, void, Trace>) => ITraceable<T, Trace>; - flatMap: <U>( - mapper: ITraceableMapper<T, ITraceable<U, Trace>, Trace>, - ) => ITraceable<U, Trace>; - flatMapAsync<U>( - mapper: ITraceableMapper<T, Promise<ITraceable<U, Trace>>, Trace>, - ): ITraceable<Promise<U>, Trace>; + flatMap: <_T>( + mapper: ITraceableMapper<T, ITraceable<_T, Trace>, Trace>, + ) => ITraceable<_T, Trace>; + flatMapAsync<_T>( + mapper: ITraceableMapper<T, Promise<ITraceable<_T, Trace>>, Trace>, + ): ITraceable<Promise<_T>, Trace>; } export class TraceableImpl<T, TraceWith> implements ITraceable<T, TraceWith> { @@ -40,20 +40,20 @@ export class TraceableImpl<T, TraceWith> implements ITraceable<T, TraceWith> { public readonly trace: ITrace<TraceWith>, ) {} - public map<U>(mapper: ITraceableMapper<T, U, TraceWith>) { + public map<_T>(mapper: ITraceableMapper<T, _T, TraceWith>) { const result = mapper(this); return new TraceableImpl(result, this.trace); } - public flatMap<U>( - mapper: ITraceableMapper<T, ITraceable<U, TraceWith>, TraceWith>, - ): ITraceable<U, TraceWith> { + public flatMap<_T>( + mapper: ITraceableMapper<T, ITraceable<_T, TraceWith>, TraceWith>, + ): ITraceable<_T, TraceWith> { return mapper(this); } - public flatMapAsync<U>( - mapper: ITraceableMapper<T, Promise<ITraceable<U, TraceWith>>, TraceWith>, - ): ITraceable<Promise<U>, TraceWith> { + public flatMapAsync<_T>( + mapper: ITraceableMapper<T, Promise<ITraceable<_T, TraceWith>>, TraceWith>, + ): ITraceable<Promise<_T>, TraceWith> { return new TraceableImpl( mapper(this).then((t) => t.get()), this.trace, @@ -65,14 +65,14 @@ export class TraceableImpl<T, TraceWith> implements ITraceable<T, TraceWith> { return this; } - public move<Tt>(t: Tt): ITraceable<Tt, TraceWith> { + public move<_T>(t: _T): ITraceable<_T, TraceWith> { return this.map(() => t); } - public bimap<U>( + public bimap<_T>( mapper: ITraceableMapper< T, - ITraceableTuple<U, Array<TraceWith> | TraceWith>, + ITraceableTuple<_T, Array<TraceWith> | TraceWith>, TraceWith >, ) { diff --git a/u/trace/logger.ts b/u/trace/logger.ts index 5890545..4f29839 100644 --- a/u/trace/logger.ts +++ b/u/trace/logger.ts @@ -95,8 +95,8 @@ export class LogTrace implements ITrace<LogTraceSupplier> { level: Math.max(logLevelOrder.indexOf(val), acc.level), }; } - const prefix = [acc.line, val].join(" "); - return { ...acc, prefix }; + const line = [acc.line, val].join(" "); + return { ...acc, line }; }, { line: "", level: -1 }, ); diff --git a/u/trace/metrics.ts b/u/trace/metrics.ts index 69322b9..822fc38 100644 --- a/u/trace/metrics.ts +++ b/u/trace/metrics.ts @@ -8,8 +8,8 @@ import { } from "@emprespresso/pengueno"; export enum Unit { - COUNT, - MILLISECONDS, + COUNT = "COUNT", + MILLISECONDS = "MILLISECONDS", } export interface IMetric { diff --git a/u/trace/trace.ts b/u/trace/trace.ts index 03605c2..6cad5b0 100644 --- a/u/trace/trace.ts +++ b/u/trace/trace.ts @@ -1,6 +1,7 @@ import { isMetricsTraceSupplier, type ITrace, + type ITraceable, type ITraceWith, LogTrace, type LogTraceSupplier, @@ -12,16 +13,14 @@ import { export class LogTraceable<T> extends TraceableImpl<T, LogTraceSupplier> { public static LogTrace = new LogTrace(); - static from<T>(t: T) { + static of<T>(t: T) { return new LogTraceable(t, LogTraceable.LogTrace); } } const getEmbeddedMetricConsumer = - (logTrace: LogTrace) => (metrics: Array<MetricValue>) => - logTrace - .addTrace("<metrics>") - .trace(JSON.stringify(metrics, null, 2) + "</metrics>"); + (logTrace: ITrace<LogTraceSupplier>) => (metrics: Array<MetricValue>) => + logTrace.trace(`<metrics>${JSON.stringify(metrics, null)}</metrics>`); export class EmbeddedMetricsTraceable<T> extends TraceableImpl< T, MetricsTraceSupplier @@ -30,11 +29,8 @@ export class EmbeddedMetricsTraceable<T> extends TraceableImpl< getEmbeddedMetricConsumer(LogTraceable.LogTrace), ); - static from<T>(t: T) { - return new EmbeddedMetricsTraceable( - t, - EmbeddedMetricsTraceable.MetricsTrace, - ); + static of<T>(t: T, metricsTrace = EmbeddedMetricsTraceable.MetricsTrace) { + return new EmbeddedMetricsTraceable(t, metricsTrace); } } @@ -72,12 +68,16 @@ export class LogMetricTraceable<T> extends TraceableImpl< T, MetricsTraceSupplier | LogTraceSupplier > { - public static LogMetricTrace = new LogMetricTrace( - LogTraceable.LogTrace, - EmbeddedMetricsTraceable.MetricsTrace, - ); + static ofLogTraceable<T>(t: ITraceable<T, LogTraceSupplier>) { + const metricsTrace = new MetricsTrace(getEmbeddedMetricConsumer(t.trace)); + return new LogMetricTraceable( + t.get(), + new LogMetricTrace(t.trace, metricsTrace), + ); + } - static from<T>(t: T) { - return new LogMetricTraceable(t, LogMetricTraceable.LogMetricTrace); + static of<T>(t: T) { + const logTrace = LogTraceable.of(t); + return LogMetricTraceable.ofLogTraceable(logTrace); } } diff --git a/worker/executor.ts b/worker/executor.ts index ea580eb..ea79995 100644 --- a/worker/executor.ts +++ b/worker/executor.ts @@ -86,7 +86,9 @@ export const executePipeline = ( .get(), ), ); - const failures = jobResults.filter((e) => e.fold(( { isLeft }) => isLeft)); + const failures = jobResults.filter((e) => + e.fold(({ isLeft }) => isLeft), + ); if (failures.length > 0) { tJobs.trace.trace(pipelinesMetric.failure); return Either.left(new Error(failures.toString())); diff --git a/worker/jobs/ci_pipeline.run b/worker/jobs/ci_pipeline.run index 03d9d6d..46237f6 100644 --- a/worker/jobs/ci_pipeline.run +++ b/worker/jobs/ci_pipeline.run @@ -3,6 +3,7 @@ import { type Command, Either, + LogTraceable, getRequiredEnvVars, getStdout, isObject, @@ -32,8 +33,8 @@ const eitherJob = getRequiredEnvVars(["remote", "refname", "rev"]) )); const ciRunMetric = Metric.fromName("checkout_ci.run"); -const trace = `checkout_ci.${run}`; -await LogMetricTraceable.from(eitherJob).bimap(TraceUtil.withTrace(trace)) +const _logJob = LogTraceable.of(eitherJob).bimap(TraceUtil.withTrace(`checkout_ci.${run}`)); +await LogMetricTraceable.ofLogTraceable(_logJob) .bimap(TraceUtil.withMetricTrace(ciRunMetric)) .map((tEitherJob) => tEitherJob.get().flatMapAsync((ciJob) => { diff --git a/worker/scripts/ansible_playbook b/worker/scripts/ansible_playbook index f2cd4b9..026f892 100755 --- a/worker/scripts/ansible_playbook +++ b/worker/scripts/ansible_playbook @@ -5,6 +5,7 @@ import { getRequiredEnvVars, getStdout, type IEither, + LogTraceable, LogMetricTraceable, Metric, prependWith, @@ -28,9 +29,8 @@ const eitherVault = Bitwarden.getConfigFromEnvironment() .mapRight((config) => new Bitwarden(config)); const playbookMetric = Metric.fromName("ansiblePlaybook.playbook"); -await LogMetricTraceable.from(eitherJob) - .bimap(TraceUtil.withTrace("ansible_playbook")) - .bimap(TraceUtil.withMetricTrace(playbookMetric)) +const _logJob = LogTraceable.of(eitherJob).bimap(TraceUtil.withTrace("ansible_playbook")); +await LogMetricTraceable.ofLogTraceable(_logJob).bimap(TraceUtil.withMetricTrace(playbookMetric)) .peek((tEitherJob) => tEitherJob.trace.trace("starting ansible playbook job! (⑅˘꒳˘)") ) diff --git a/worker/scripts/build_docker_image b/worker/scripts/build_docker_image index 2e19111..f2fa522 100755 --- a/worker/scripts/build_docker_image +++ b/worker/scripts/build_docker_image @@ -4,6 +4,7 @@ import { getRequiredEnvVars, getStdout, LogLevel, + LogTraceable, LogMetricTraceable, Metric, TraceUtil, @@ -34,14 +35,14 @@ const eitherVault = Bitwarden.getConfigFromEnvironment() const buildImageMetric = Metric.fromName("dockerImage.build"); const loginMetric = Metric.fromName("dockerRegistry.login"); -await LogMetricTraceable.from(eitherJob) - .bimap( +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]; }, - ) + ); +await LogMetricTraceable.ofLogTraceable(_logJob) .bimap(TraceUtil.withMetricTrace(buildImageMetric)) .bimap(TraceUtil.withMetricTrace(loginMetric)) .peek((tEitherJob) => diff --git a/worker/secret.ts b/worker/secret.ts index f5ae93f..951c539 100644 --- a/worker/secret.ts +++ b/worker/secret.ts @@ -1,36 +1,36 @@ import { - Either, - getRequiredEnvVars, - getStdout, - type IEither, - type ITraceable, - type LogMetricTraceSupplier, - Metric, - TraceUtil, + Either, + getRequiredEnvVars, + getStdout, + type IEither, + type ITraceable, + type LogMetricTraceSupplier, + Metric, + TraceUtil, } from "@emprespresso/pengueno"; // -- <ISecret> -- export interface LoginItem { - login: { - username: string; - password: string; - }; + login: { + username: string; + password: string; + }; } export interface SecureNote { - notes: string; + 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>>; + unlock: (client: TClient) => Promise<IEither<Error, TKey>>; + lock: (client: TClient, key: TKey) => Promise<IEither<Error, TKey>>; - fetchSecret: <T extends SecretItem>( - client: TClient, - key: TKey, - item: TItemId, - ) => Promise<IEither<Error, T>>; + fetchSecret: <T extends SecretItem>( + client: TClient, + key: TKey, + item: TItemId, + ) => Promise<IEither<Error, T>>; } // -- </ISecret> -- @@ -39,178 +39,156 @@ type TClient = ITraceable<unknown, LogMetricTraceSupplier>; type TKey = string; type TItemId = string; export class Bitwarden implements IVault<TClient, TKey, TItemId> { - constructor(private readonly config: BitwardenConfig) {} + constructor(private readonly config: BitwardenConfig) {} - public unlock(client: TClient) { - return client - .move(this.config) - .bimap(TraceUtil.withMetricTrace(Bitwarden.loginMetric)) - .flatMap((tConfig) => - tConfig - .move(`bw config server ${tConfig.get().server}`) - .map(getStdout), - ) - .map(async (tEitherWithConfig) => { - const eitherWithConfig = await tEitherWithConfig.get(); - tEitherWithConfig.trace.trace("logging in~ ^.^"); - return eitherWithConfig.flatMapAsync((_) => - 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" - ], - ), - ); - }) - .map(async (tEitherWithAuthd) => { - const eitherWithAuthd = await tEitherWithAuthd.get(); - tEitherWithAuthd.trace.trace( - "unlocking the secret vault~ (◕ᴗ◕✿)", - ); - return eitherWithAuthd.flatMapAsync((_) => - 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(); - } + public unlock(client: TClient) { + return client + .move(this.config) + .bimap(TraceUtil.withMetricTrace(Bitwarden.loginMetric)) + .flatMap((tConfig) => + tConfig.move(`bw config server ${tConfig.get().server}`).map(getStdout), + ) + .map(async (tEitherWithConfig) => { + const eitherWithConfig = await tEitherWithConfig.get(); + tEitherWithConfig.trace.trace("logging in~ ^.^"); + return eitherWithConfig.flatMapAsync((_) => + 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"], + ), + ); + }) + .map(async (tEitherWithAuthd) => { + const eitherWithAuthd = await tEitherWithAuthd.get(); + tEitherWithAuthd.trace.trace("unlocking the secret vault~ (◕ᴗ◕✿)"); + return eitherWithAuthd.flatMapAsync((_) => + 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(); + } - public fetchSecret<T extends SecretItem>( - client: TClient, - key: string, - item: string, - ): Promise<IEither<Error, T>> { - return client - .move(key) - .bimap(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() }, - }), - ), - ) - .map( - TraceUtil.promiseify((tEitherItemsJson) => - tEitherItemsJson - .get() - .flatMap( - ( - itemsJson, - ): IEither<Error, Array<T & { name: string }>> => - Either.fromFailable(() => - JSON.parse(itemsJson), - ), - ) - .flatMap((itemsList): IEither<Error, T> => { - const secret = itemsList.find( - ({ name }) => name === item, - ); - if (!secret) { - return Either.left( - new Error( - `couldn't find the item ${item} (。•́︿•̀。)`, - ), - ); - } - return Either.right(secret); - }), - ), + public fetchSecret<T extends SecretItem>( + client: TClient, + key: string, + item: string, + ): Promise<IEither<Error, T>> { + return client + .move(key) + .bimap(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() }, + }), + ), + ) + .map( + TraceUtil.promiseify((tEitherItemsJson) => + tEitherItemsJson + .get() + .flatMap( + (itemsJson): IEither<Error, Array<T & { name: string }>> => + Either.fromFailable(() => JSON.parse(itemsJson)), ) - .peek(async (tEitherWithSecret) => { - const eitherWithSecret = await tEitherWithSecret.get(); - return tEitherWithSecret.trace.trace( - eitherWithSecret.fold( - ({ isLeft }) => - Bitwarden.fetchSecretMetric[ - isLeft ? "failure" : "success" - ], - ), + .flatMap((itemsList): IEither<Error, T> => { + const secret = itemsList.find(({ name }) => name === item); + if (!secret) { + return Either.left( + new Error(`couldn't find the item ${item} (。•́︿•̀。)`), ); - }) - .get(); - } + } + return Either.right(secret); + }), + ), + ) + .peek(async (tEitherWithSecret) => { + const eitherWithSecret = await tEitherWithSecret.get(); + return tEitherWithSecret.trace.trace( + eitherWithSecret.fold( + ({ isLeft }) => + Bitwarden.fetchSecretMetric[isLeft ? "failure" : "success"], + ), + ); + }) + .get(); + } - public lock(client: TClient, key: TKey) { - return client - .move(key) - .bimap(TraceUtil.withMetricTrace(Bitwarden.lockVaultMetric)) - .peek((tSession) => - tSession.trace.trace(`taking care of locking the vault :3`), - ) - .flatMap((tSession) => - tSession - .move("bw lock") - .map((lockCmd) => - getStdout(lockCmd, { - env: { BW_SESSION: tSession.get() }, - }), - ), - ) - .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~ (。•̀ᴗ-)✧", - ); - }); - }) - .get(); - } + public lock(client: TClient, key: TKey) { + return client + .move(key) + .bimap(TraceUtil.withMetricTrace(Bitwarden.lockVaultMetric)) + .peek((tSession) => + tSession.trace.trace(`taking care of locking the vault :3`), + ) + .flatMap((tSession) => + tSession.move("bw lock").map((lockCmd) => + getStdout(lockCmd, { + env: { BW_SESSION: tSession.get() }, + }), + ), + ) + .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~ (。•̀ᴗ-)✧", + ); + }); + }) + .get(); + } - public static getConfigFromEnvironment(): IEither<Error, BitwardenConfig> { - return getRequiredEnvVars([ - "BW_SERVER", - "BW_CLIENTSECRET", - "BW_CLIENTID", - "BW_PASSWORD", - ]).mapRight(({ BW_SERVER, BW_CLIENTSECRET, BW_CLIENTID }) => ({ - clientId: BW_CLIENTID, - secret: BW_CLIENTSECRET, - server: BW_SERVER, - })); - } + public static getConfigFromEnvironment(): IEither<Error, BitwardenConfig> { + return getRequiredEnvVars([ + "BW_SERVER", + "BW_CLIENTSECRET", + "BW_CLIENTID", + "BW_PASSWORD", + ]).mapRight(({ BW_SERVER, BW_CLIENTSECRET, BW_CLIENTID }) => ({ + clientId: BW_CLIENTID, + secret: BW_CLIENTSECRET, + server: BW_SERVER, + })); + } - 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"); + private static unlockVaultMetric = Metric.fromName("Bitwarden.unlockVault"); + private static fetchSecretMetric = Metric.fromName("Bitwarden.fetchSecret"); + private static lockVaultMetric = Metric.fromName("Bitwarden.lock"); } export interface BitwardenConfig { - server: string; - secret: string; - clientId: string; + server: string; + secret: string; + clientId: string; } // -- </IVault> -- |