summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElizabeth Alexander Hunt <me@liz.coffee>2025-05-13 09:50:15 -0700
committerElizabeth Alexander Hunt <me@liz.coffee>2025-05-13 09:50:15 -0700
commit2543ac8b11af11f034836591046cdb52911f9403 (patch)
treed2fa475348c9867aab2994e9914895a57db88d75
parente49fda41176d025a671802be76c219d66167276f (diff)
downloadci-2543ac8b11af11f034836591046cdb52911f9403.tar.gz
ci-2543ac8b11af11f034836591046cdb52911f9403.zip
snapshot
-rw-r--r--[-rwxr-xr-x]hooks/mod.ts274
-rw-r--r--hooks/queuer.ts21
-rwxr-xr-xhooks/server.ts272
-rw-r--r--utils/either.ts29
-rw-r--r--utils/isObject.ts2
-rw-r--r--utils/mod.ts1
-rw-r--r--utils/run.ts11
-rw-r--r--utils/trace.ts5
-rw-r--r--utils/validate_identifier.ts12
-rwxr-xr-xworker/scripts/build_image13
10 files changed, 345 insertions, 295 deletions
diff --git a/hooks/mod.ts b/hooks/mod.ts
index 9df7d67..738003c 100755..100644
--- a/hooks/mod.ts
+++ b/hooks/mod.ts
@@ -1,272 +1,2 @@
-#!/usr/bin/env -S deno run --allow-env --allow-net --allow-run
-
-import {
- getRequiredEnv,
- getStdout,
- invalidExecutionEntriesOf,
- Traceable,
- ITraceable,
- ITraceableLogger,
- IEither,
- Either,
-} from "@liz-ci/utils";
-import type { Job } from "@liz-ci/model";
-
-const SERVER_CONFIG = {
- host: "0.0.0.0",
- port: 9000,
-};
-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"]);
- 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 },
- );
- }
- });
- }
-}
-
-const aPost = (req: Traceable<Request>): IEither<Response, Request> => {
- const {item: request, logger} = req;
- const {method} = request;
- if (method !== "POST") {
- const msg = "that's not how you pet me (⋟﹏⋞) try post instead~";
- logger.warn(msg);
- return { err: new Response(msg + "\n", {status: 405}) };
- }
- return {ok: request};
-};
-
-type JsonTransformer<JsonT, R> = (json: Traceable<JsonT>) => Either<string, R>;
-const aJson = <BodyT, JsonT = unknown>(jsonTransformer: JsonTransformer<JsonT, BodyT>) =>
- (r: Traceable<Request>) => r
- .map(async ({item: request, logger}): Promise<Either<string, JsonT>> => {
- try {
- return {ok: <JsonT>(await request.json())};
- } catch (e) {
- const err = "seems to be invalid JSON (>//<) can you fix?";
- logger.warn(err);
- return { err };
- }
- })
- .flatMapAsync(TraceableImpl.promiseify((t): Traceable<Either<string, BodyT>> => {
- const {item: {err, ok: json}} = t;
- if (err) return t.map(() => ({ err }));
- return t.map(() => json!).map(jsonTransformer);
- }))
-
-
-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(
- { logger, item }: Traceable<unknown>,
- ): Either<string, Job> {
- const isObject = (o: unknown): o is Record<string, unknown> =>
- typeof o === "object" && !Array.isArray(o) && !!o;
- if (!isObject(item) || !isObject(item.arguments)|| typeof item.type !== "string") {
- const err = "seems like a pwetty mawfomed job \\(-.-)/";
- logger.warn(err)
- return { err };
- }
-
- const ok = { type: item.type, arguments: item.arguments };
- const invalidArgEntries = invalidExecutionEntriesOf({type: ok.type, ...ok.arguments});
- if (invalidArgEntries.length > 0) {
- const err = "your reqwest seems invawid (´。﹏。`) can you fix? uwu\n" + invalidArgEntries;
- logger.warn(err);
- return { err };
- }
-
- return { ok: <Job>ok };
- }
-
- public processHook(r: Traceable<Request>) {
- return r.map(aPost)
- .map((t) => {
- either((err) => ({ err }), (request) => )
- const {item: {ok: request, err}} = t;
- if (err) return <Traceable<{ err: Response }>> t;
- return t.map(() => request!).map(aJson(this.getJob));
- });
-// flatMapAsync(aJsonPost(this.getJob))
-// .map(TraceableImpl.promiseify((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();
- }
-
- // 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();
-});
+export * from "./queuer.ts";
+export * from "./server.ts";
diff --git a/hooks/queuer.ts b/hooks/queuer.ts
index 1461809..d2987ca 100644
--- a/hooks/queuer.ts
+++ b/hooks/queuer.ts
@@ -1,20 +1,22 @@
-import type { IEither, ITraceable, ITraceableLogger } from "@liz-ci/utils";
+import { getStdout, type IEither, type Traceable } from "@liz-ci/utils";
import type { Job } from "@liz-ci/model";
type QueuePosition = string;
export class QueueError extends Error {}
export interface IJobQueuer<TJob> {
- queue: <L extends ITraceableLogger<L>>(
- job: ITraceable<TJob, L>,
+ queue: (
+ job: TJob,
) => Promise<IEither<QueueError, QueuePosition>>;
}
-export class LaminarJobQueuer implements IJobQueuer<Job> {
+export class LaminarJobQueuer implements IJobQueuer<Traceable<Job>> {
constructor(
private readonly queuePositionPrefix: string,
) {}
- public async queue({ item: job, logger }: Traceable<Job>) {
+ public async queue(j: Traceable<Job>) {
+ const { item: job, logger: _logger } = j;
+ const logger = _logger.addTracer(() => `[LaminarJobQueuer.queue.${job}]`);
const laminarCommand = [
"laminarc",
"queue",
@@ -27,12 +29,7 @@ export class LaminarJobQueuer implements IJobQueuer<Job> {
laminarCommand,
);
- return (await getStdout(laminarCommand)).mapBoth(
- (e) => {
- const err = `we bwoke oh noesss D:`;
- logger.error(err, e);
- return Either.left<QueueError, QueuePosition>(e);
- },
+ return (await getStdout(j.map(() => laminarCommand))).mapRight(
(stdout) => {
logger.log(stdout);
@@ -40,7 +37,7 @@ export class LaminarJobQueuer implements IJobQueuer<Job> {
const jobUrl = `${this.queuePositionPrefix}/jobs/${jobName}/${jobId}`;
logger.log(`all queued up and weady to go~ (˘ω˘) => ${jobUrl}\n`);
- return Either.right<QueueError, QueuePosition>(jobUrl);
+ return jobUrl;
},
);
}
diff --git a/hooks/server.ts b/hooks/server.ts
new file mode 100755
index 0000000..000c391
--- /dev/null
+++ b/hooks/server.ts
@@ -0,0 +1,272 @@
+#!/usr/bin/env -S deno run --allow-env --allow-net --allow-run
+
+import {
+ Either,
+ getRequiredEnv,
+ getStdout,
+ IEither,
+ isObject,
+ Traceable,
+ validateExecutionEntries,
+} from "@liz-ci/utils";
+import { IJobQueuer } from "./mod.ts";
+import type { Job } from "@liz-ci/model";
+
+const SERVER_CONFIG = {
+ host: "0.0.0.0",
+ port: 9000,
+};
+interface IHealthCheckActivity<R> {
+ healthCheck(req: R): Promise<Response>;
+}
+
+class HealthCheckActivity implements IHealthCheckActivity<Traceable<Request>> {
+ public async healthCheck(
+ req: Traceable<Request>,
+ ) {
+ return await req.bimap(Traceable.withClassTrace(this))
+ .map(async (r) => {
+ const { logger } = r;
+ try {
+ getRequiredEnv("LAMINAR_HOST");
+ await getStdout(r.map(() => ["laminarc", "show-jobs"]));
+ 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 },
+ );
+ }
+ }).item;
+ }
+}
+
+const aPost = (req: Traceable<Request>): IEither<Response, Request> => {
+ const { item: request, logger: _logger } = req;
+ const logger = _logger.addTracer(() => "[aPost]");
+
+ if (request.method !== "POST") {
+ const msg = "that's not how you pet me (⋟﹏⋞) try post instead~";
+ logger.warn(msg);
+ return Either.left(new Response(msg + "\n", { status: 405 }));
+ }
+ return Either.right(request);
+};
+
+type JsonTransformer<JsonT, R> = (json: Traceable<JsonT>) => Either<string, R>;
+const aJson =
+ <BodyT, JsonT = unknown>(jsonTransformer: JsonTransformer<JsonT, BodyT>) =>
+ async (r: Traceable<Request>): Promise<Either<string, BodyT>> => {
+ const { item: request, logger } = r;
+ try {
+ return Either.right<string, JsonT>(await request.json())
+ .mapRight(r.move)
+ .flatMap(jsonTransformer);
+ } catch (_e) {
+ const err = "seems to be invalid JSON (>//<) can you fix?";
+ logger.warn(err);
+ return Either.left(err);
+ }
+ };
+
+interface IJobHookActivity<R> {
+ processHook(req: R): Promise<Response>;
+}
+type GetJobRequest = { jobType: string; args: unknown };
+class JobHookActivityImpl implements IJobHookActivity<Traceable<Request>> {
+ constructor(private readonly queuer: IJobQueuer<Traceable<Job>>) {}
+
+ private getJob<JsonT>(
+ u: Traceable<JsonT>,
+ ): Either<string, Job> {
+ const { logger: _logger, item } = u;
+ const logger = _logger.addTracer(() => "[getJob]");
+ const couldBeJsonJob = isObject(item) && "arguments" in item &&
+ "type" in item && item;
+ const couldBeArguments = couldBeJsonJob && isObject(item.arguments) &&
+ item.arguments;
+ if (!couldBeJsonJob) {
+ const err = "seems like a pwetty mawfomed job \\(-.-)/";
+ logger.warn(err);
+ return Either.left(err);
+ }
+
+ return validateExecutionEntries({
+ type: item.type,
+ ...couldBeArguments,
+ }).mapBoth((err) => {
+ const e = "your reqwest seems invawid (´。﹏。`) can you fix? uwu\n" +
+ err.toString();
+ logger.warn(e);
+ return e;
+ }, (_ok) => <Job> item);
+ }
+
+ public async processHook(r: Traceable<Request>) {
+ return await r.bimap(Traceable.withClassTrace(this)).map(aPost)
+ .map(aJson(this.getJob));
+ }
+ // flatMapAsync(aJsonPost(this.getJob))
+ // .map(TraceableImpl.promiseify((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();
+ }
+
+ // 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/either.ts b/utils/either.ts
index d21c796..10e4f43 100644
--- a/utils/either.ts
+++ b/utils/either.ts
@@ -5,14 +5,37 @@ export interface IEither<E, T> {
errBranch: (e: E) => Ee,
okBranch: (o: T) => Tt,
) => IEither<Ee, Tt>;
+ mapRight: <Tt>(mapper: (t: T) => Tt) => Either<E, Tt>;
+ mapLeft: <Ee>(mapper: (e: E) => Ee) => Either<Ee, T>;
+ flatMap: <Ee extends E, Tt>(
+ mapper: (e: T) => Either<Ee, Tt>,
+ ) => Either<Ee, Tt>;
}
export class Either<E, T> implements IEither<E, T> {
private constructor(readonly err?: E, readonly ok?: T) {}
- public mapBoth<Ee, Tt>(errBranch: (e: E) => Ee, okBranch: (t: T) => Tt) {
- if (this.err) return new Either<Ee, Tt>(errBranch(this.err));
- return new Either<Ee, Tt>(undefined, okBranch(this.ok!));
+ public mapBoth<Ee, Tt>(
+ errBranch: (e: E) => Ee,
+ okBranch: (t: T) => Tt,
+ ): Either<Ee, Tt> {
+ if (this.err) return Either.left(errBranch(this.err));
+ return Either.right(okBranch(this.ok!));
+ }
+
+ public flatMap<Ee extends E, Tt>(mapper: (t: T) => Either<Ee, Tt>) {
+ if (this.ok) return mapper(this.ok);
+ return this;
+ }
+
+ public mapRight<Tt>(mapper: (t: T) => Tt): Either<E, Tt> {
+ if (this.ok) return Either.right(mapper(this.ok));
+ return Either.left(this.err!);
+ }
+
+ public mapLeft<Ee>(mapper: (e: E) => Ee): Either<Ee, T> {
+ if (this.err) return Either.left(mapper(this.err));
+ return Either.right(this.ok!);
}
static left<E, T>(e: E) {
diff --git a/utils/isObject.ts b/utils/isObject.ts
new file mode 100644
index 0000000..73f7f80
--- /dev/null
+++ b/utils/isObject.ts
@@ -0,0 +1,2 @@
+export const isObject = (o: unknown): o is object =>
+ typeof o === "object" && !Array.isArray(o) && !!o;
diff --git a/utils/mod.ts b/utils/mod.ts
index 53ea173..d8cb526 100644
--- a/utils/mod.ts
+++ b/utils/mod.ts
@@ -1,3 +1,4 @@
+export * from "./isObject.ts";
export * from "./trace.ts";
export * from "./either.ts";
export * from "./env.ts";
diff --git a/utils/run.ts b/utils/run.ts
index 06e7d9f..9093863 100644
--- a/utils/run.ts
+++ b/utils/run.ts
@@ -1,10 +1,13 @@
-import { Either } from "./mod.ts";
+import { Either, type Traceable } from "./mod.ts";
export class ProcessError extends Error {}
export const getStdout = async (
- cmd: string[] | string,
+ { item: cmd, logger: _logger }: Traceable<string[] | string>,
options: Deno.CommandOptions = {},
): Promise<Either<ProcessError, string>> => {
+ const logger = _logger.addTracer(() => "[getStdout]");
+
+ logger.log(`:> im gonna run this command!`, cmd);
const [exec, ...args] = (typeof cmd === "string") ? cmd.split(" ") : cmd;
const command = new Deno.Command(exec, {
args,
@@ -19,12 +22,16 @@ export const getStdout = async (
const stderrText = new TextDecoder().decode(stderr);
if (code !== 0) {
+ logger.error(`i weceived an exit code of ${code} i wanna zeroooo :<`);
return Either.left<ProcessError, string>(
new ProcessError(`command failed\n${stderrText}`),
);
}
+
+ logger.log("yay! i got code 0 :3", cmd);
return Either.right<ProcessError, string>(stdoutText);
} catch (e) {
+ logger.error(`o.o wat`, e);
if (e instanceof Error) {
return Either.left<ProcessError, string>(e);
}
diff --git a/utils/trace.ts b/utils/trace.ts
index 373f37e..1a5e51d 100644
--- a/utils/trace.ts
+++ b/utils/trace.ts
@@ -31,6 +31,7 @@ export interface ITraceable<T, L extends ITraceableLogger<L>> {
flatMapAsync<U>(
mapper: ITraceableMapper<T, L, Promise<ITraceable<U, L>>>,
): ITraceable<Promise<U>, L>;
+ move<Tt>(t: Tt): ITraceable<Tt, L>;
}
export class TraceableLogger implements ITraceableLogger<TraceableLogger> {
@@ -96,6 +97,10 @@ class TraceableImpl<
return this;
}
+ public move<Tt>(t: Tt) {
+ return this.map(() => t);
+ }
+
public bimap<U>(mapper: ITraceableMapper<T, L, ITraceableTuple<U>>) {
const [item, trace] = mapper(this);
return new TraceableImpl(item, this.logger.addTracer(trace));
diff --git a/utils/validate_identifier.ts b/utils/validate_identifier.ts
index e2a1dc5..ec8b77b 100644
--- a/utils/validate_identifier.ts
+++ b/utils/validate_identifier.ts
@@ -1,11 +1,17 @@
+import { Either } from "./mod.ts";
+
export const validateIdentifier = (token: string) => {
return (/^[a-zA-Z0-9_\-:. \/]+$/).test(token) && !token.includes("..");
};
-export const invalidExecutionEntriesOf = (
+// ensure {@param obj} is a Record<string, string> with stuff that won't
+// have the potential for shell injection, just to be super safe.
+export const validateExecutionEntries = (
obj: Record<string, unknown>,
-): Array<[string, unknown]> => {
- return Object.entries(obj).filter((e) =>
+): Either<Array<[string, unknown]>, Record<string, string>> => {
+ const invalidEntries = Object.entries(obj).filter((e) =>
!e.every((x) => typeof x === "string" && validateIdentifier(x))
);
+ if (invalidEntries.length > 0) return Either.left(invalidEntries);
+ return Either.right(<Record<string, string>> obj);
};
diff --git a/worker/scripts/build_image b/worker/scripts/build_image
index a4dcdf4..6618af6 100755
--- a/worker/scripts/build_image
+++ b/worker/scripts/build_image
@@ -27,7 +27,9 @@ const logger = loggerWithPrefix(() =>
);
const run = async () => {
- logger.log("starting docker image build job~ (⑅˘꒳˘) let's make something cute!");
+ logger.log(
+ "starting docker image build job~ (⑅˘꒳˘) let's make something cute!",
+ );
const bitwardenSession = new BitwardenSession();
const { username: registryUsername, password: registryPassword } =
@@ -44,9 +46,14 @@ const run = async () => {
"--username",
registryUsername,
"--password",
- registryPassword,
+ "$DOCKER_PASSWORD",
args.registry,
],
+ {
+ env: {
+ DOCKER_PASSWORD: registryPassword,
+ },
+ },
);
const tag =
@@ -77,7 +84,7 @@ const run = async () => {
"push",
tag,
];
- logger.log(`sending image to registry~ (>ᴗ<)`, pushCmd);
+ logger.log(`sending image to wegistwy~ (>ᴗ<)`, pushCmd);
await getStdout(pushCmd);
};