summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElizabeth Alexander Hunt <me@liz.coffee>2025-05-12 09:40:12 -0700
committerElizabeth Alexander Hunt <me@liz.coffee>2025-05-12 09:54:58 -0700
commit723fa00cb14513eb1a517728d4464c4f148a29cc (patch)
treed32e2f725397d41b3ad7f886d61c16458dde5b37
parent30729a0cf707d9022bae0a7baaba77379dc31fd5 (diff)
downloadci-723fa00cb14513eb1a517728d4464c4f148a29cc.tar.gz
ci-723fa00cb14513eb1a517728d4464c4f148a29cc.zip
The big refactor
-rw-r--r--.ci/ci.ts4
-rw-r--r--README.md18
-rwxr-xr-xhooks/mod.ts321
-rw-r--r--utils/env.ts2
-rw-r--r--utils/logger.ts6
-rw-r--r--utils/mod.ts2
-rw-r--r--utils/run.ts2
-rw-r--r--utils/secret.ts42
-rw-r--r--utils/trace.ts111
-rw-r--r--utils/validate_identifier.ts8
-rwxr-xr-xworker/jobs/checkout_ci.run12
-rwxr-xr-xworker/scripts/ansible_playbook22
-rwxr-xr-xworker/scripts/build_image12
-rwxr-xr-xworker/scripts/fetch_code8
-rwxr-xr-xworker/scripts/run_pipeline31
15 files changed, 477 insertions, 124 deletions
diff --git a/.ci/ci.ts b/.ci/ci.ts
index b79a830..9531b18 100644
--- a/.ci/ci.ts
+++ b/.ci/ci.ts
@@ -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);
}
diff --git a/README.md b/README.md
index d242cee..4495934 100644
--- a/README.md
+++ b/README.md
@@ -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) {