summaryrefslogtreecommitdiff
path: root/worker
diff options
context:
space:
mode:
Diffstat (limited to 'worker')
-rw-r--r--worker/Dockerfile20
-rw-r--r--worker/deno.json4
-rw-r--r--worker/jobs/checkout_ci.run39
-rw-r--r--worker/mod.ts0
-rw-r--r--worker/scripts/ansible_playbook0
-rw-r--r--worker/scripts/build_image67
-rw-r--r--worker/scripts/fetch_code6
-rw-r--r--worker/scripts/run_pipeline28
8 files changed, 164 insertions, 0 deletions
diff --git a/worker/Dockerfile b/worker/Dockerfile
new file mode 100644
index 0000000..e3a8f7b
--- /dev/null
+++ b/worker/Dockerfile
@@ -0,0 +1,20 @@
+FROM oci.liz.coffee/img/liz-ci:release as worker
+
+RUN addgroup docker
+RUN adduser -SDh /var/lib/laminar -g 'Laminar' -G users -G docker laminar
+
+# Secret retrieval
+RUN apk add nodejs npm jq
+RUN npm install -g @bitwarden/cli
+
+# Image building / publishing jobs
+RUN apk add docker-cli
+
+# Ansible playbooks
+RUN apk add ansible-core openssh
+
+USER laminar
+WORKDIR /var/lib/laminar
+EXPOSE 8080
+
+CMD [ "/usr/sbin/laminard" ]
diff --git a/worker/deno.json b/worker/deno.json
new file mode 100644
index 0000000..5636d0a
--- /dev/null
+++ b/worker/deno.json
@@ -0,0 +1,4 @@
+{
+ "name": "@liz-ci/worker",
+ "exports": "./mod.ts"
+}
diff --git a/worker/jobs/checkout_ci.run b/worker/jobs/checkout_ci.run
new file mode 100644
index 0000000..d47697d
--- /dev/null
+++ b/worker/jobs/checkout_ci.run
@@ -0,0 +1,39 @@
+#!/bin/bash
+# usage: laminarc run ci remote="ssh://src.liz.coffee:2222/cgit" rev="<sha>" \
+# refname="refs/..."
+
+set -e
+
+RUN=`date +%s`
+WORKING_DIR=`$PWD/$RUN`
+
+mkdir -p "$WORKING_DIR" && cd "$WORKING_DIR"
+
+checkout="$rev" path="tmpsrc" fetch_code.sh
+
+if [[ ! -e "$WORKING_DIR/tmpsrc/.ci/ci.json" ]]; then
+ echo "No Continuous Integration configured for $remote."
+ exit 0
+fi
+
+PIPELINE_GENERATOR_PATH=$(jq -r '.pipeline' "$WORKING_DIR/tmpsrc/.ci/ci.json")
+if [[ "$PIPELINE_GENERATOR_PATH" == *".."* ]]; then
+ echo "Error: Path contains '..'"
+ exit 1
+fi
+
+docker run --rm \
+ --network none \
+ --cap-drop ALL \
+ --security-opt no-new-privileges \
+ -v "$WORKING_DIR/tmpsrc/$PIPELINE_GENERATOR:/pipeline" \
+ -e refname="$refname" \
+ -e rev="$rev" \
+ -e remote="$remote" \
+ oci.liz.coffee/img/liz-ci:release \
+ /pipeline \
+ > "$WORKING_DIR/pipeline.json"
+
+rm -rf tmpsrc
+
+pipeline="$WORKING_DIR/pipeline.json" run_pipeline
diff --git a/worker/mod.ts b/worker/mod.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/worker/mod.ts
diff --git a/worker/scripts/ansible_playbook b/worker/scripts/ansible_playbook
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/worker/scripts/ansible_playbook
diff --git a/worker/scripts/build_image b/worker/scripts/build_image
new file mode 100644
index 0000000..ba1ec8f
--- /dev/null
+++ b/worker/scripts/build_image
@@ -0,0 +1,67 @@
+#!/usr/bin/env -S deno run --allow-env --allow-net
+
+import type { BuildDockerImageJobProps } from "@liz-ci/model";
+import {
+ BitwardenSession,
+ getRequiredEnv,
+ getStdout,
+ type LoginItem,
+} from "@liz-ci/utils";
+
+const args: BuildDockerImageJobProps = {
+ registry: getRequiredEnv("registry"),
+ namespace: getRequiredEnv("namespace"),
+ repository: getRequiredEnv("repository"),
+ imageTag: getRequiredEnv("imageTag"),
+
+ context: getRequiredEnv("context"),
+ dockerfile: getRequiredEnv("dockerfile"),
+ buildTarget: getRequiredEnv("buildTarget"),
+};
+
+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");
+}
+
+await getStdout(
+ [
+ "docker",
+ "login",
+ "--username",
+ registryUsername,
+ "--password",
+ registryPassword,
+ args.registry,
+ ],
+);
+
+const tag =
+ `${args.registry}/${args.namespace}/${args.repository}:${args.imageTag}`;
+await getStdout(
+ [
+ "docker",
+ "build",
+ "--target",
+ args.buildTarget,
+ "-t",
+ tag,
+ "-f",
+ `${args.dockerfile}`,
+ `${args.context}`,
+ ],
+ {
+ clearEnv: true,
+ env: {},
+ },
+);
+
+await getStdout(
+ [
+ "docker",
+ "push",
+ tag,
+ ],
+);
diff --git a/worker/scripts/fetch_code b/worker/scripts/fetch_code
new file mode 100644
index 0000000..d45f6db
--- /dev/null
+++ b/worker/scripts/fetch_code
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+git clone "$remote" "$path"
+cd "$path"
+git reset --hard "$checkout"
+cd -
diff --git a/worker/scripts/run_pipeline b/worker/scripts/run_pipeline
new file mode 100644
index 0000000..ad58573
--- /dev/null
+++ b/worker/scripts/run_pipeline
@@ -0,0 +1,28 @@
+#!/usr/bin/env -S deno --allow-env --allow-net --allow-read
+
+import { type Job, PipelineImpl } from "@liz-ci/model";
+import { getRequiredEnv, getStdout, validateIdentifier } from "@liz-ci/utils";
+
+const stages = await (Deno.readTextFile(getRequiredEnv("pipeline")))
+ .then(PipelineImpl.from)
+ .then((pipeline) => pipeline.getStages());
+
+const validateJob = (job: Job) => {
+ Object.entries(job.arguments).forEach((e) => {
+ if (!e.every(validateIdentifier)) {
+ throw new Error(`job of type ${job.type} has invalid entry ${e}`);
+ }
+ });
+};
+
+for (const stage of stages) {
+ await Promise.all(
+ stage.parallelJobs.map((job) => {
+ validateJob(job);
+
+ return getStdout(job.type, {
+ env: job.arguments,
+ });
+ }),
+ );
+}