diff options
author | Elizabeth Alexander Hunt <me@liz.coffee> | 2025-05-12 09:40:12 -0700 |
---|---|---|
committer | Elizabeth Alexander Hunt <me@liz.coffee> | 2025-05-12 09:54:58 -0700 |
commit | 723fa00cb14513eb1a517728d4464c4f148a29cc (patch) | |
tree | d32e2f725397d41b3ad7f886d61c16458dde5b37 | |
parent | 30729a0cf707d9022bae0a7baaba77379dc31fd5 (diff) | |
download | ci-723fa00cb14513eb1a517728d4464c4f148a29cc.tar.gz ci-723fa00cb14513eb1a517728d4464c4f148a29cc.zip |
The big refactor
-rw-r--r-- | .ci/ci.ts | 4 | ||||
-rw-r--r-- | README.md | 18 | ||||
-rwxr-xr-x | hooks/mod.ts | 321 | ||||
-rw-r--r-- | utils/env.ts | 2 | ||||
-rw-r--r-- | utils/logger.ts | 6 | ||||
-rw-r--r-- | utils/mod.ts | 2 | ||||
-rw-r--r-- | utils/run.ts | 2 | ||||
-rw-r--r-- | utils/secret.ts | 42 | ||||
-rw-r--r-- | utils/trace.ts | 111 | ||||
-rw-r--r-- | utils/validate_identifier.ts | 8 | ||||
-rwxr-xr-x | worker/jobs/checkout_ci.run | 12 | ||||
-rwxr-xr-x | worker/scripts/ansible_playbook | 22 | ||||
-rwxr-xr-x | worker/scripts/build_image | 12 | ||||
-rwxr-xr-x | worker/scripts/fetch_code | 8 | ||||
-rwxr-xr-x | worker/scripts/run_pipeline | 31 |
15 files changed, 477 insertions, 124 deletions
@@ -79,5 +79,7 @@ const getPipeline = () => { }; if (import.meta.main) { - console.log(getPipeline().serialize()); + const encoder = new TextEncoder(); + const data = encoder.encode(getPipeline().serialize()); + await Deno.stdout.write(data); } @@ -1,4 +1,16 @@ -# LizCI +# lizci (⑅˘꒳˘) -its my very own ci, built on top of [Laminar](https://laminar.ohwg.net/docs.html), -cuz i thought it was coooool. +hi! this is lizci, built on top of [laminar](https://laminar.ohwg.net/docs.html), because while jenkins looks hot and classy, +i prefer a simpler approach that i can extend on myself. + +## how to use? (。•̀ᴗ-)✧ + +add a script that generates the + +1. add a `.ci/ci.json` file to your repo +2. point to your pipeline generator script +3. push your code +4. watch the magic happen! + + +made with love and deno~ diff --git a/hooks/mod.ts b/hooks/mod.ts index f858ad0..63a05fc 100755 --- a/hooks/mod.ts +++ b/hooks/mod.ts @@ -3,76 +3,289 @@ import { getRequiredEnv, getStdout, - loggerWithPrefix, - validateIdentifier, + invalidExecutionEntriesOf, + type Traceable, + TraceableImpl, } from "@liz-ci/utils"; +import type { Job } from "@liz-ci/model"; -const getRequestLogger = (req: Request) => { - const url = new URL(req.url); - const traceId = crypto.randomUUID(); - const getPrefix = () => - `[${ - new Date().toISOString() - }] RequestTrace=[${traceId}] @ [${url.pathname}] -X [${req.method}] |`; - return loggerWithPrefix(getPrefix); +const SERVER_CONFIG = { + host: "0.0.0.0", + port: 9000, }; -const addr = { port: 9000, hostname: "0.0.0.0" }; -Deno.serve(addr, async (req) => { - const logger = getRequestLogger(req); - logger.log("start"); +type QueuePosition = string; +interface IJobQueuer { + queue: ( + job: Traceable<Job>, + ) => Promise<{ ok?: QueuePosition; err?: unknown }>; +} - try { - const { pathname } = new URL(req.url); - if (pathname === "/health") { +class LaminarJobQueuer implements IJobQueuer { + constructor( + private readonly queuePositionPrefix: string, + ) {} + + public async queue({ item: job, logger }: Traceable<Job>) { + try { + const laminarCommand = [ + "laminarc", + "queue", + job.type, + ...Object.entries(job.arguments).map(([key, val]) => + `"${key}"="${val}"` + ), + ]; + + logger.log( + `im so excited to see how this queue job will end!! (>ᴗ<)`, + laminarCommand, + ); + + const output = await getStdout(laminarCommand); + logger.log(output); + + const [jobName, jobId] = output.split(":"); + const jobUrl = `${this.queuePositionPrefix}/jobs/${jobName}/${jobId}`; + + logger.log(`all queued up and weady to go~ (˘ω˘) => ${jobUrl}\n`); + return { ok: jobUrl, err: undefined }; + } catch (e) { + return { ok: undefined, err: e }; + } + } +} + +interface IHealthCheckActivity { + healthCheck(req: Traceable<Request>): Traceable<Promise<Response>>; +} + +class HealthCheckActivity implements IHealthCheckActivity { + public healthCheck( + req: Traceable<Request>, + ) { + return req.map(async ({ logger }) => { try { getRequiredEnv("LAMINAR_HOST"); await getStdout(["laminarc", "show-jobs"]); - return new Response("think im healthy!! lets get to work.\n", { - status: 200, - }); - } catch (e) { - logger.error(e); - return new Response("i need to eat more vegetables -.-\n", { - status: 500, - }); + const msg = `think im healthy!! (✿˘◡˘) ready to do work~\n`; + logger.log(msg); + return new Response( + msg, + { status: 200 }, + ); + } catch (error) { + logger.error(error); + return new Response( + "oh no, i need to eat more vegetables (。•́︿•̀。)...\n", + { status: 500 }, + ); } + }); + } +} + +interface IJobHookActivity { + processHook(req: Traceable<Request>): Traceable<Promise<Response>>; +} +type GetJobRequest = { jobType: string; args: unknown }; +class JobHookActivityImpl implements IJobHookActivity { + constructor(private readonly queuer: IJobQueuer) {} + + private getJob( + { item: { args, jobType }, logger }: Traceable<GetJobRequest>, + ): { ok?: Job; err?: string } { + if (Array.isArray(args) || typeof args !== "object" || args === null) { + return { err: "your reqwest seems compwetewy mawfomed (-.-)/\n" }; } - if (req.method !== "POST") { - return new Response("invalid method", { - status: 405, - }); + const invalidArgEntries = invalidExecutionEntriesOf({ + ...args, + jobType, + }); + if (invalidArgEntries.length > 0) { + const err = "your reqwest seems invawid (´。﹏。`) can you fix? uwu"; + logger.error(err, jobType, args); + return { err }; } - if (pathname === "/checkout_ci") { - const { remote, rev, refname } = await req.json(); - if (![remote, rev, refname].every(validateIdentifier)) { - logger.log("invalid reqwest"); - return new Response("invalid reqwest >:D\n", { - status: 400, - }); + return { + ok: <Job> { + type: jobType, + arguments: args, + }, + }; + } + + public processHook(r: Traceable<Request>) { + return r.map(async ({ item: request, logger }) => { + const { method } = request; + if (method !== "POST") { + const msg = "that's not how you pet me (⋟﹏⋞) try post instead~"; + logger.log(msg); + const r405 = new Response(msg, { status: 405 }); + return { err: r405 }; } - const laminar = await getStdout([ - "laminarc", - "queue", - "checkout_ci", - `remote="${remote}"`, - `rev="${rev}"`, - `refname="${refname}"`, - ]); - logger.log(`successful queue :D`, laminar); - return new Response(laminar + "\n", { - status: 200, - }); + const jobType = new URL(request.url).pathname.split("/")[1]; + const args = await request.json(); + return { ok: <GetJobRequest> { jobType, args } }; + }) + .map(TraceableImpl.promiseify((g) => { + const { item: { ok: jobRequest, err } } = g; + if (jobRequest) { + return g.map(() => jobRequest) + .map(this.getJob) + .map( + ({ item: { ok: jobRequest, err } }) => { + if (err) return { err: new Response(err, { status: 400 }) }; + return { ok: jobRequest }; + }, + ); + } + return g.map(() => ({ ok: undefined, err })); + })) + .map(TraceableImpl.promiseify(({ item: t }) => { + const { item: { ok: job, err } } = t; + if (err) return t.map(() => Promise.resolve(err)); + + return t.map(() => job!) + .map(this.queuer.queue) + .map(TraceableImpl.promiseify(({ item, logger }) => { + if (item.ok) { + return new Response(item.ok, { status: 200 }); + } + logger.error(item.err); + return new Response("i messed up D:\n", { status: 500 }); + })); + })); + } +} + +class LizCIServerImpl implements ILizCIServer { + constructor( + private readonly healthCheckActivity: IHealthCheckActivity, + private readonly jobHookActivity: IJobHookActivity, + ) {} + + private route( + req: Traceable<Request & { pathname: string }>, + ): Traceable<Promise<Response>> { + return req.flatMap((req) => { + const { logger, item: { method, pathname } } = req; + if (pathname === "/health") { + return this.healthCheckActivity.healthCheck(req); + } + return this.jobHookActivity.processHook(req); + }); + } + + public async serve(req: Request): Promise<Response> { + const traceId = crypto.randomUUID(); + const { pathname } = new URL(req.url); + const traceSupplier = () => `[${traceId} <- ${req.method}'d @ ${pathname}]`; + return TraceableImpl.from(req) + .bimap(({ item: req }) => [{ req, pathname }, traceSupplier]) + .flatMap(this.route) + .map(({ item, logger }) => + item.catch((e) => { + const errorMessage = `oh noes! something went wrong (ಥ_ಥ) so sowwy!`; + logger.error(errorMessage, e); + return new Response(`${errorMessage}\n`, { status: 500 }); + }) + ) + .item; + } +} + +class JobQueue { + private readonly logger: PrefixLogger; + private readonly url: URL; + private readonly pathname: string; + + constructor(private readonly request: Request, private readonly) { + this.url = new URL(request.url); + this.pathname = this.url.pathname; + this.logger = this.createLogger(); + } + + /** + * Creates a logger with request-specific context + */ + + /** + * Performs health checks on dependent services + */ + private async performHealthCheck(): Promise<void> { + } + + /** + * Handles health check requests + */ + private async handleHealthCheck(): Promise<Response> { + try { + await this.performHealthCheck(); + } catch (error) { + } + } + + /** + * Queues a job in the laminar system + */ + private async queueJob(jobName: string, args: JobRequest): Promise<Response> { + } + + /** + * Validates job request parameters + */ + private validateJobRequest( + jobName: string, + args: unknown, + ): { valid: boolean; response?: Response } { + } + + /** + * Main method to handle the request + */ + public async handle(): Promise<Response> { + this.logger.log("go! :DDD"); + + // Handle health check requests + if (this.pathname === "/health") { + return this.handleHealthCheck(); } - return new Response("idk what that is bro :((\n", { status: 404 }); - } catch (e) { - logger.error("uncaught exception", e); - return new Response("womp womp D:\n", { status: 500 }); - } finally { - logger.log("finish"); + // Validate HTTP method + if (this.request.method !== "POST") { + } + + // Extract job name from path + + if (!validation.valid) { + return validation.response!; + } + + // Queue the job + return this.queueJob(jobName, requestBody as JobRequest); + } + + /** + * Handles the entire request lifecycle, including error handling + */ + public async processRequest(): Promise<Response> { + try { + return await this.handle(); + } catch (error) { + } finally { + this.logger.log("allll done!"); + } } +} + +/** + * Entry point - starts the server + */ +Deno.serve(SERVER_CONFIG, async (request: Request) => { + const handler = new RequestHandler(request); + return handler.processRequest(); }); diff --git a/utils/env.ts b/utils/env.ts index c0cf447..31b7ccf 100644 --- a/utils/env.ts +++ b/utils/env.ts @@ -1,5 +1,5 @@ export const getRequiredEnv = (name: string): string => { const value = Deno.env.get(name); - if (!value) throw new Error(`${name} environment variable is required`); + if (!value) throw new Error(`environment variable "${name}" is required D:`); return value; }; diff --git a/utils/logger.ts b/utils/logger.ts deleted file mode 100644 index e36d249..0000000 --- a/utils/logger.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const loggerWithPrefix = (prefixSupplier: () => string) => { - return { - log: (...args: unknown[]) => console.log(prefixSupplier(), ...args), - error: (...args: unknown[]) => console.error(prefixSupplier(), ...args), - }; -}; diff --git a/utils/mod.ts b/utils/mod.ts index 4e907df..a8a0751 100644 --- a/utils/mod.ts +++ b/utils/mod.ts @@ -1,4 +1,4 @@ -export * from "./logger.ts"; +export * from "./trace.ts"; export * from "./env.ts"; export * from "./run.ts"; export * from "./secret.ts"; diff --git a/utils/run.ts b/utils/run.ts index f06ef97..60ae1e6 100644 --- a/utils/run.ts +++ b/utils/run.ts @@ -15,7 +15,7 @@ export const getStdout = async ( const stdoutText = new TextDecoder().decode(stdout); const stderrText = new TextDecoder().decode(stderr); - if (code !== 0) throw new Error(`Command failed\n${stderrText}`); + if (code !== 0) throw new Error(`command failed\n${stderrText}`); return stdoutText; }; diff --git a/utils/secret.ts b/utils/secret.ts index eb2054b..0faa97c 100644 --- a/utils/secret.ts +++ b/utils/secret.ts @@ -1,35 +1,47 @@ -import { getRequiredEnv, getStdout, loggerWithPrefix } from "./mod.ts"; +import { + getRequiredEnv, + getStdout, + loggerWithPrefix, + type PrefixLogger, +} from "./mod.ts"; -const logger = loggerWithPrefix(() => - `[${new Date().toISOString()}] [BitwardenSession]` -); export class BitwardenSession { private readonly sessionInitializer: Promise<string>; + private readonly logger: PrefixLogger; - constructor(server = getRequiredEnv("BW_SERVER")) { + constructor( + server = getRequiredEnv("BW_SERVER"), + logger = loggerWithPrefix(), + ) { ["BW_CLIENTID", "BW_CLIENTSECRET"].forEach(getRequiredEnv); + const instanceId = crypto.randomUUID().split("-").at(0); + this.logger = logger.withAdditionalPrefix(() => + `[BitwardenSession.instance.${instanceId}]` + ); + this.sessionInitializer = getStdout( `bw config server ${server} --quiet`, ) .then(() => { - logger.log("Logging in via API"); + this.logger.log("logging in via api (˘ω˘)"); return getStdout(`bw login --apikey --quiet`); }) .then(() => { - logger.log("Unlocking vault in session"); + this.logger.log("unlocking the secret vault~ (◕ᴗ◕✿)"); return getStdout(`bw unlock --passwordenv BW_PASSWORD --raw`); }) .then((session) => { - logger.log(`Session ${session}`); - return session.trim(); + const _session = session.trim(); + this.logger.log(`got my session key (>ᴗ<) ${_session}`); + return _session; }); } public async getItem<T extends LoginItem | SecureNote>( secretName: string, ): Promise<T> { - logger.log(`Finding secret ${secretName}`); + this.logger.log(`looking for your secret ${secretName} (⑅˘꒳˘)`); return await this.sessionInitializer.then((session) => getStdout(`bw list items`, { env: { @@ -39,8 +51,12 @@ export class BitwardenSession { ).then((items) => JSON.parse(items)).then((items) => items.find(({ name }: { name: string }) => name === secretName) ).then((item) => { - if (!item) throw new Error("Could not find bitwarden item " + secretName); - logger.log(`Found secret: ${secretName}`); + if (!item) { + throw new Error( + "couldn't find the bitwarden item " + secretName + " (。•́︿•̀。)", + ); + } + this.logger.log(`yay! found secret: ${secretName} (*ˊᗜˋ*)`); return item; }); } @@ -49,7 +65,7 @@ export class BitwardenSession { return await this.sessionInitializer.then((session) => getStdout(`bw lock`, { env: { BW_SESSION: session } }) ).then(() => { - logger.log("Locked session"); + this.logger.log("all locked up and secure now~ (。•̀ᴗ-)✧"); }); } } diff --git a/utils/trace.ts b/utils/trace.ts new file mode 100644 index 0000000..737aa60 --- /dev/null +++ b/utils/trace.ts @@ -0,0 +1,111 @@ +export interface Logger { + log: (...args: unknown[]) => void; + debug: (...args: unknown[]) => void; + warn: (...args: unknown[]) => void; + error: (...args: unknown[]) => void; +} + +type Supplier<T> = () => T; +type TraceSupplier = Supplier<string>; +export interface ITraceableLogger<L extends ITraceableLogger<L>> + extends Logger { + addTracer: (traceSupplier: TraceSupplier) => L; +} + +export type ITraceableTuple<T> = [T, TraceSupplier]; +export type ITraceableMapper<T, L extends ITraceableLogger<L>, U> = ( + t: ITraceable<T, L>, +) => U; +export interface ITraceable<T, L extends ITraceableLogger<L>> { + item: T; + logger: L; + + map: <U>(mapper: ITraceableMapper<T, L, U>) => ITraceable<U, L>; + bimap: <U>( + mapper: ITraceableMapper<T, L, ITraceableTuple<U>>, + ) => ITraceable<U, L>; + peek: (peek: ITraceableMapper<T, L, void>) => ITraceable<T, L>; + flatMap: <U>(mapper: ITraceableMapper<T, L, ITraceable<U, L>>) => ITraceable<U, L>; + flatMapAsync<U>(mapper: ITraceableMapper<T, L, Promise<ITraceable<U, L>>>): ITraceable<Promise<U>, L>; +} + +export class TraceableLogger + implements ITraceableLogger<TraceableLogger> { + private readonly logger: Logger = console; + constructor( + private readonly traces = [() => `[${new Date().toISOString()}]`], + ) { + } + + public debug(...args: unknown[]) { + this.logger.debug("[DEBUG]", ...this.getPrefix(), args); + } + + public log(...args: unknown[]) { + this.logger.log("[INFO]", ...this.getPrefix(), args); + } + + public warn(...args: unknown[]) { + this.logger.warn("[WARN]", ...this.getPrefix(), args); + } + + public error(...args: unknown[]) { + this.logger.error("[ERROR]", ...this.getPrefix(), args); + } + + public addTracer(traceSupplier: TraceSupplier) { + return new TraceableLogger(this.traces.concat(traceSupplier)); + } + + private getPrefix() { + return this.traces.map((tracer) => tracer()); + } +} + +export class TraceableImpl< + T, + L extends ITraceableLogger<L>, +> implements ITraceable<T, L> { + private constructor(readonly item: T, readonly logger: L) {} + + public map<U>(mapper: ITraceableMapper<T, L, U>) { + const result = mapper(this); + return new TraceableImpl(result, this.logger); + } + + public flatMap<U>(mapper: ITraceableMapper<T, L, ITraceable<U, L>>): ITraceable<U, L> { + return mapper(this); + } + + public flatMapAsync<U>(mapper: ITraceableMapper<T, L, Promise<ITraceable<U, L>>>): ITraceable<Promise<U>, L> { + return new TraceableImpl(mapper(this).then((i) => ) + } + + public peek(peek: ITraceableMapper<T, L, void>) { + peek(this); + return this; + } + + public bimap<U>(mapper: ITraceableMapper<T, L, ITraceableTuple<U>>) { + const [item, trace] = mapper(this); + return new TraceableImpl(item, this.logger.addTracer(trace)); + } + + static promiseify<T, L extends ITraceableLogger<L>, U>( + mapper: ITraceableMapper<T, L, U>, + ): ITraceableMapper<Promise<T>, L, Promise<U>> { + return (traceablePromise) => traceablePromise.map( + async ({ item: promise }) => { + const t = await promise; + return traceablePromise.map(() => t).map(mapper).item; + }); +// return (traceable) => +// traceable.item.then((item) => mapper(new TraceableImpl(item, traceable.logger))); + } + + static from<T>(t: T) { + return new TraceableImpl(t, new TraceableLogger()); + } +} + +export interface Traceable<T> extends ITraceable<T, TraceableLogger> diff --git a/utils/validate_identifier.ts b/utils/validate_identifier.ts index 0c9242c..c204497 100644 --- a/utils/validate_identifier.ts +++ b/utils/validate_identifier.ts @@ -1,3 +1,11 @@ export const validateIdentifier = (token: string) => { return (/^[a-zA-Z0-9_\-:. \/]+$/).test(token) && !token.includes(".."); }; + +export const invalidExecutionEntriesOf = ( + obj: Record<string, string>, +): Array<[string, string]> => { + return Object.entries(obj).filter((e) => + !e.every((x) => typeof x === "string" && validateIdentifier(x)) + ); +}; diff --git a/worker/jobs/checkout_ci.run b/worker/jobs/checkout_ci.run index 0945444..6416bfe 100755 --- a/worker/jobs/checkout_ci.run +++ b/worker/jobs/checkout_ci.run @@ -8,7 +8,7 @@ WORKING_DIR="$PWD/$RUN" export LOG_PREFIX="[checkout_ci.$RUN]" -log "starting checkout_ci job $remote @ $refname - $rev in $WORKING_DIR" +log "hewwo~ starting checkout job for $remote @ $refname - $rev" mkdir -p "$WORKING_DIR" && cd "$WORKING_DIR" CODE="$WORKING_DIR/src" @@ -16,17 +16,17 @@ checkout="$rev" path="$CODE" fetch_code CI_WORKFLOW="$CODE/.ci/ci.json" if [[ ! -e "$CI_WORKFLOW" ]]; then - log "no CI configuration found" + log "couldn't find any ci configuration (。•́︿•̀。) that's okay~" exit 0 fi PIPELINE_GENERATOR_PATH=$(jq -r '.pipeline' "$CI_WORKFLOW") if [[ "$PIPELINE_GENERATOR_PATH" == *".."* ]]; then - log "no '..'" + log "found sneaky '..' in path (⋟﹏⋞) that's not allowed!" exit 1 fi -log "building the pipeline..." +log "building the pipeline~ (◕ᴗ◕✿) let's make something amazing!" PIPELINE="$WORKING_DIR/pipeline.json" docker run --rm --network none --cap-drop ALL --security-opt no-new-privileges \ -e refname="$refname" -e rev="$rev" -e remote="$remote" \ @@ -36,7 +36,7 @@ docker run --rm --network none --cap-drop ALL --security-opt no-new-privileges \ pipeline="$PIPELINE" run_pipeline -log "cleaning up working directory" +log "cleaning up after myself like a good kitty (˘ω˘)" cd "$RETURN" && rm -rf "$WORKING_DIR" -log "checkout_ci run done" +log "all done with checkout! hope it worked~ (⑅˘꒳˘)" diff --git a/worker/scripts/ansible_playbook b/worker/scripts/ansible_playbook index 062680d..d24cbb6 100755 --- a/worker/scripts/ansible_playbook +++ b/worker/scripts/ansible_playbook @@ -14,12 +14,12 @@ const args: AnsiblePlaybookJobProps = { path: getRequiredEnv("path"), playbooks: getRequiredEnv("playbooks"), }; -const logger = loggerWithPrefix(() => - `[${new Date().toISOString()}] [ansible_playbook.'${args.playbooks}']` -); +const logger = loggerWithPrefix(() => `[ansible_playbook."${args.playbooks}"]`); const run = async () => { - logger.log("Starting Ansible playbook job"); + logger.log( + "starting ansible playbook job~ (⑅˘꒳˘) let's configure all the things!", + ); const bitwardenSession = new BitwardenSession(); const secretFiles = await Promise.all( @@ -30,7 +30,7 @@ const run = async () => { .then(async ({ notes: recoveredSecret }) => { const tempFile = await Deno.makeTempFile(); await Deno.writeTextFile(tempFile, recoveredSecret); - logger.log(secretName, "stored at", tempFile); + logger.log(secretName, "safely tucked away at", tempFile, "(˘ω˘)"); return tempFile; }) ), @@ -52,27 +52,31 @@ const run = async () => { "willhallonline/ansible:latest", ...playbookCmd.split(" "), ]; - logger.log("deploying...", deployCmd); + logger.log("running ansible magic~ (◕ᴗ◕✿)", deployCmd); await getStdout(deployCmd); } finally { await Promise.allSettled( [bitwardenSession.close()].concat( secretFiles.map((p) => { - logger.log(`cleanup`, p); + logger.log(`tidying up`, p, "keeping things neat and tidy~"); return Deno.remove(p); }), ), ); } - logger.log("ansible playbook job completed"); + logger.log("ansible playbook job all done! servers are happy now (。•̀ᴗ-)✧"); }; if (import.meta.main) { try { await run(); } catch (e) { - logger.error("womp womp D:", e); + logger.error( + "oh nyo! ansible had a problem", + e, + "maybe next time? (´。﹏。`)", + ); throw e; } } diff --git a/worker/scripts/build_image b/worker/scripts/build_image index 07c07c9..a4dcdf4 100755 --- a/worker/scripts/build_image +++ b/worker/scripts/build_image @@ -27,16 +27,16 @@ const logger = loggerWithPrefix(() => ); const run = async () => { - logger.log("Starting Docker image build job"); + logger.log("starting docker image build job~ (⑅˘꒳˘) let's make something cute!"); const bitwardenSession = new BitwardenSession(); const { username: registryUsername, password: registryPassword } = (await bitwardenSession.getItem<LoginItem>(args.registry))?.login ?? {}; if (!(registryUsername && registryPassword)) { - throw new Error("where's the login info bruh"); + throw new Error("oh nyo! can't find the login info (。•́︿•̀。)"); } - logger.log(`Logging in to Docker registry: ${args.registry}`); + logger.log(`logging in to docker registry: ${args.registry} (˘ω˘)`); await getStdout( [ "docker", @@ -63,7 +63,7 @@ const run = async () => { `${args.context}`, ]; - logger.log(`building`, tag, buildCmd); + logger.log(`building image~ (◕ᴗ◕✿)`, tag, buildCmd); await getStdout( buildCmd, { @@ -77,7 +77,7 @@ const run = async () => { "push", tag, ]; - logger.log(`pushing`, pushCmd); + logger.log(`sending image to registry~ (>ᴗ<)`, pushCmd); await getStdout(pushCmd); }; @@ -85,7 +85,7 @@ if (import.meta.main) { try { await run(); } catch (e) { - logger.error("womp womp D:", e); + logger.error("oh nyo! something went wrong with the build (´。﹏。`)", e); throw e; } } diff --git a/worker/scripts/fetch_code b/worker/scripts/fetch_code index d3af763..83a5612 100755 --- a/worker/scripts/fetch_code +++ b/worker/scripts/fetch_code @@ -2,18 +2,18 @@ export LOG_PREFIX="[fetch_code $remote @ $checkout -> $path]" -log "fetch!" +log "getting the codez~ time to fetch!" git clone "$remote" "$path" if [ ! $? -eq 0 ]; then - log "D: failed to clone" + log "oh nyo! couldn't clone the repo" exit 1 fi cd "$path" -log "checkout $checkout" +log "switching to $checkout like a good kitty~" git reset --hard "$checkout" if [ ! $? -eq 0 ]; then - log "D: can't reset to $checkout" + log "ouchie! can't reset to $checkout" cd - exit 1 fi diff --git a/worker/scripts/run_pipeline b/worker/scripts/run_pipeline index 9991001..abb13b3 100755 --- a/worker/scripts/run_pipeline +++ b/worker/scripts/run_pipeline @@ -1,11 +1,11 @@ #!/usr/bin/env -S deno run --allow-env --allow-net --allow-run --allow-read --allow-write -import { type Job, PipelineImpl } from "@liz-ci/model"; +import { PipelineImpl } from "@liz-ci/model"; import { getRequiredEnv, getStdout, + invalidExecutionEntriesOf, loggerWithPrefix, - validateIdentifier, } from "@liz-ci/utils"; const pipelinePath = getRequiredEnv("pipeline"); @@ -13,39 +13,32 @@ const logger = loggerWithPrefix(() => `[${new Date().toISOString()}] [run_pipeline.${pipelinePath}]` ); -const jobValidForExecution = (job: Job) => { - return Object - .entries(job.arguments) - .filter((e) => { - if (e.every(validateIdentifier)) return true; - logger.error(`job of type ${job.type} has invalid args ${e}`); - return false; - }) - .length === 0; -}; - const run = async () => { - logger.log("starting pipeline execution"); + logger.log("starting pipeline execution~ time to work hard!"); const stages = await (Deno.readTextFile(pipelinePath)) .then(PipelineImpl.from) .then((pipeline) => pipeline.getStages()); for (const stage of stages) { - logger.log("executing stage", stage); + logger.log("executing stage. do your best little stage :>", stage); await Promise.all( stage.parallelJobs.map(async (job, jobIdx) => { - logger.log(`executing job ${jobIdx}`, job); - if (!jobValidForExecution(job)) throw new Error("invalid job"); + logger.log(`let's do this little job ok!! ${jobIdx}`, job); + const invalidArgs = invalidExecutionEntriesOf(job.arguments); + if (invalidArgs.length) { + logger.error(`oh nooes`, invalidArgs); + throw new Error("invalid job arguments"); + } const result = await getStdout(job.type, { env: job.arguments }); - logger.log(jobIdx, "outputs", { result }); + logger.log(jobIdx, "brought something to you! look :D", { result }); }), ); } - logger.log("ok! yay!"); + logger.log("all done! everything worked! yay~ (⑅˘꒳˘)"); }; if (import.meta.main) { |