summaryrefslogtreecommitdiff
path: root/worker/jobs
diff options
context:
space:
mode:
authorElizabeth Alexander Hunt <me@liz.coffee>2025-05-12 09:40:12 -0700
committerElizabeth <me@liz.coffee>2025-05-26 14:15:42 -0700
commitd51c9d74857aca3c2f172609297266968bc7f809 (patch)
tree64327f9cc4219729aa11af32d7d4c70cddfc2292 /worker/jobs
parent30729a0cf707d9022bae0a7baaba77379dc31fd5 (diff)
downloadci-d51c9d74857aca3c2f172609297266968bc7f809.tar.gz
ci-d51c9d74857aca3c2f172609297266968bc7f809.zip
The big refactor TM
Diffstat (limited to 'worker/jobs')
-rwxr-xr-xworker/jobs/checkout_ci.run42
-rw-r--r--worker/jobs/ci_pipeline.run166
2 files changed, 166 insertions, 42 deletions
diff --git a/worker/jobs/checkout_ci.run b/worker/jobs/checkout_ci.run
deleted file mode 100755
index 0945444..0000000
--- a/worker/jobs/checkout_ci.run
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/bin/bash
-# usage: laminarc run checkout_ci remote="ssh://src.liz.coffee:2222/cgit" rev="<sha>" \
-# refname="refs/..."
-
-RUN=`date +%s`
-RETURN="$PWD"
-WORKING_DIR="$PWD/$RUN"
-
-export LOG_PREFIX="[checkout_ci.$RUN]"
-
-log "starting checkout_ci job $remote @ $refname - $rev in $WORKING_DIR"
-mkdir -p "$WORKING_DIR" && cd "$WORKING_DIR"
-
-CODE="$WORKING_DIR/src"
-checkout="$rev" path="$CODE" fetch_code
-
-CI_WORKFLOW="$CODE/.ci/ci.json"
-if [[ ! -e "$CI_WORKFLOW" ]]; then
- log "no CI configuration found"
- exit 0
-fi
-
-PIPELINE_GENERATOR_PATH=$(jq -r '.pipeline' "$CI_WORKFLOW")
-if [[ "$PIPELINE_GENERATOR_PATH" == *".."* ]]; then
- log "no '..'"
- exit 1
-fi
-
-log "building the pipeline..."
-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" \
- -v "$CODE/$PIPELINE_GENERATOR_PATH:/pipeline_generator" \
- oci.liz.coffee/img/liz-ci:release /pipeline_generator \
- > "$PIPELINE"
-
-pipeline="$PIPELINE" run_pipeline
-
-log "cleaning up working directory"
-cd "$RETURN" && rm -rf "$WORKING_DIR"
-
-log "checkout_ci run done"
diff --git a/worker/jobs/ci_pipeline.run b/worker/jobs/ci_pipeline.run
new file mode 100644
index 0000000..337bd53
--- /dev/null
+++ b/worker/jobs/ci_pipeline.run
@@ -0,0 +1,166 @@
+import {
+ type Command,
+ Either,
+ getRequiredEnvVars,
+ getStdout,
+ isObject,
+ LogMetricTraceable,
+ Metric,
+ prependWith,
+ TraceUtil,
+} from "@emprespresso/pengueno";
+import {
+ type CheckoutCiJob,
+ type FetchCodeJob,
+ PipelineImpl,
+} from "@emprespresso/ci-model";
+import { executeJob, executePipeline } from "@emprespresso/ci-worker";
+
+const run = Date.now().toString();
+const eitherJob = getRequiredEnvVars(["remote", "refname", "rev"])
+ .mapRight((baseArgs) => (
+ <CheckoutCiJob> {
+ type: "checkout_ci",
+ arguments: {
+ ...baseArgs,
+ run,
+ returnPath: Deno.cwd(),
+ },
+ }
+ ));
+
+const ciRunMetric = Metric.fromName("checkout_ci.run");
+const trace = `checkout_ci.${run}`;
+await LogMetricTraceable.from(eitherJob).bimap(TraceUtil.withTrace(trace))
+ .bimap(TraceUtil.withMetricTrace(ciRunMetric))
+ .map((tEitherJob) =>
+ tEitherJob.get().flatMapAsync((ciJob) => {
+ const wd = getWorkingDirectoryForCiJob(ciJob);
+ const fetchPackageJob = <FetchCodeJob> {
+ type: "fetch_code",
+ arguments: {
+ remoteUrl: ciJob.arguments.remote,
+ checkout: ciJob.arguments.rev,
+ path: getSrcDirectoryForCiJob(ciJob),
+ },
+ };
+ return Either.fromFailableAsync<Error, CheckoutCiJob>(
+ Deno.mkdir(wd).then(() => Deno.chdir(wd))
+ .then(() => tEitherJob.move(fetchPackageJob).map(executeJob).get())
+ .then(() => ciJob),
+ );
+ })
+ )
+ .map((tEitherCiJob) =>
+ tEitherCiJob.get().then((eitherCiJob) =>
+ eitherCiJob.flatMapAsync<{ cmd: Command; job: CheckoutCiJob }>((ciJob) =>
+ Either.fromFailableAsync<Error, string>(
+ Deno.readTextFile(
+ `${getSrcDirectoryForCiJob(ciJob)}/${CI_WORKFLOW_FILE}`,
+ ),
+ ).then((eitherWorkflowJson) =>
+ eitherWorkflowJson.flatMap(
+ (json) => Either.fromFailable<Error, unknown>(JSON.parse(json)),
+ ).flatMap((eitherWorkflowParse) => {
+ if (isCiWorkflow(eitherWorkflowParse)) {
+ return Either.right({
+ cmd: getPipelineGenerationCommand(
+ ciJob,
+ eitherWorkflowParse.workflow,
+ ),
+ job: ciJob,
+ });
+ }
+ return Either.left(
+ new Error(
+ "couldn't find any valid ci configuration (。•́︿•̀。), that's okay~",
+ ),
+ );
+ })
+ )
+ )
+ )
+ )
+ .map(async (tEitherPipelineGenerationCommand) => {
+ const eitherJobCommand = await tEitherPipelineGenerationCommand.get();
+ const eitherPipeline = await eitherJobCommand
+ .flatMapAsync((jobCommand) =>
+ tEitherPipelineGenerationCommand.move(jobCommand.cmd)
+ .map(getStdout)
+ .get()
+ );
+ return eitherPipeline
+ .flatMap(PipelineImpl.from)
+ .flatMap((pipeline) =>
+ eitherJobCommand.mapRight(({ job }) => ({ job, pipeline }))
+ );
+ })
+ .peek(
+ TraceUtil.promiseify((tEitherPipeline) =>
+ tEitherPipeline.get()
+ .mapRight((val) => val.pipeline.serialize())
+ .mapRight((pipeline) =>
+ `built the pipeline~ (◕ᴗ◕✿) let's make something amazing! ${pipeline}`
+ )
+ .mapRight((msg) => tEitherPipeline.trace.trace(msg))
+ ),
+ )
+ .map(
+ async (tEitherPipeline) => {
+ const eitherPipeline = await tEitherPipeline.get();
+ return eitherPipeline.flatMapAsync(({ pipeline, job }) =>
+ tEitherPipeline.move(pipeline)
+ .map((p) =>
+ executePipeline(p, {
+ HOME: getWorkingDirectoryForCiJob(job),
+ })
+ )
+ .get()
+ );
+ },
+ )
+ .get()
+ .then((e) =>
+ e.flatMap(() => eitherJob).fold((err, val) => {
+ if (err) throw err;
+ return Deno.remove(getWorkingDirectoryForCiJob(val), { recursive: true });
+ })
+ );
+
+const getWorkingDirectoryForCiJob = (job: CheckoutCiJob) =>
+ `${job.arguments.returnPath}/${job.arguments.run}`;
+
+const getSrcDirectoryForCiJob = (job: CheckoutCiJob) =>
+ `${job.arguments.returnPath}/${job.arguments.run}/src`;
+
+const _runFlags = ("--rm --network none --cap-drop ALL" +
+ "--security-opt no-new-privileges").split(" ");
+const _image = "oci.liz.coffee/img/ci-worker:release";
+const getPipelineGenerationCommand = (
+ job: CheckoutCiJob,
+ pipelineGeneratorPath: string,
+ image = _image,
+ runFlags = _runFlags,
+) => [
+ "docker",
+ "run",
+ ...runFlags,
+ ...prependWith(
+ Object.entries(job.arguments).map(([key, val]) => `"${key}"="${val}"`),
+ "-e",
+ ),
+ "-v",
+ `${
+ getSrcDirectoryForCiJob(job)
+ }/${pipelineGeneratorPath}:/pipeline_generator`,
+ image,
+ "/pipeline_generator",
+];
+
+export interface CiWorkflow {
+ workflow: string;
+}
+export const isCiWorkflow = (t: unknown): t is CiWorkflow =>
+ isObject(t) && "workflow" in t && typeof t.workflow === "string" &&
+ !t.workflow.includes("..");
+const CI_WORKFLOW_FILE = ".ci/ci.json";