summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElizabeth Alexander Hunt <me@liz.coffee>2025-05-10 17:31:06 -0700
committerElizabeth Alexander Hunt <me@liz.coffee>2025-05-10 18:13:23 -0700
commit3a06e32e2724bcc349bbbfa93c08c23a7c732ad4 (patch)
treeb71504605a45e504cc31cc9ecc22a6346b149c32
parentfa8f3f9465e87d499f7d6428323f496a884b7818 (diff)
downloadci-3a06e32e2724bcc349bbbfa93c08c23a7c732ad4.tar.gz
ci-3a06e32e2724bcc349bbbfa93c08c23a7c732ad4.zip
Flesh out ansible playbook job.
-rw-r--r--.ci/ci.ts1
-rw-r--r--Dockerfile2
-rw-r--r--hooks/Dockerfile2
-rw-r--r--hooks/mod.ts5
-rw-r--r--model/job.ts1
-rw-r--r--utils/secret.ts7
-rw-r--r--worker/scripts/ansible_playbook67
7 files changed, 79 insertions, 6 deletions
diff --git a/.ci/ci.ts b/.ci/ci.ts
index c876762..b79a830 100644
--- a/.ci/ci.ts
+++ b/.ci/ci.ts
@@ -67,6 +67,7 @@ const getPipeline = () => {
const thenDeploy: AnsiblePlaybookJob = {
type: "ansible_playbook",
arguments: {
+ path: "infra",
playbooks: "playbooks/ci.yml",
},
};
diff --git a/Dockerfile b/Dockerfile
index 3e1e6dd..4b94b21 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,7 +8,7 @@ RUN cmake -B /opt/laminar/build -S /opt/laminar/src -G Ninja -DCMAKE_BUILD_TYPE=
&& cmake --build /opt/laminar/build \
&& cmake --install /opt/laminar/build --strip
-FROM denoland/deno:alpine as liz-ci
+FROM denoland/deno:alpine AS liz-ci
RUN apk add --no-cache capnproto sqlite-libs zlib curl
COPY --from=laminar /usr/sbin/laminard /usr/sbin/laminard
diff --git a/hooks/Dockerfile b/hooks/Dockerfile
index a287e2f..61fc0f5 100644
--- a/hooks/Dockerfile
+++ b/hooks/Dockerfile
@@ -1,3 +1,3 @@
-FROM oci.liz.coffee/img/liz-ci:release as hooks
+FROM oci.liz.coffee/img/liz-ci:release AS hooks
CMD [ "deno", "run", "--allow-env", "--allow-net", "/app/hooks/mod.ts" ]
diff --git a/hooks/mod.ts b/hooks/mod.ts
index f432b72..a8f649c 100644
--- a/hooks/mod.ts
+++ b/hooks/mod.ts
@@ -1,9 +1,10 @@
#!/usr/bin/env -S deno run --allow-env --allow-net
-import { getStdout, validateIdentifier } from "@liz-ci/utils";
+import { getStdout, validateIdentifier, getRequiredEnv } from "@liz-ci/utils";
-const addr = { port: 9000, hostname: "0.0.0.0" };
+getRequiredEnv("LAMINAR_HOST");
+const addr = { port: 9000, hostname: "0.0.0.0" };
Deno.serve(addr, async (req) => {
const { pathname } = new URL(req.url);
if (pathname === "/health") {
diff --git a/model/job.ts b/model/job.ts
index 96e0959..53d548f 100644
--- a/model/job.ts
+++ b/model/job.ts
@@ -32,6 +32,7 @@ export interface BuildDockerImageJob extends Job {
}
export interface AnsiblePlaybookJobProps extends JobArgT {
+ readonly path: string;
readonly playbooks: string;
}
diff --git a/utils/secret.ts b/utils/secret.ts
index 9847aa6..8860998 100644
--- a/utils/secret.ts
+++ b/utils/secret.ts
@@ -15,7 +15,7 @@ export class BitwardenSession {
public async getItem<T extends LoginItem | SecureNote>(
secretName: string,
- ): Promise<T | undefined> {
+ ): Promise<T> {
return await this.sessionInitializer.then((session) =>
getStdout(`bw list items`, {
env: {
@@ -24,7 +24,10 @@ 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);
+ return item;
+ });
}
async close(): Promise<void> {
diff --git a/worker/scripts/ansible_playbook b/worker/scripts/ansible_playbook
index e69de29..bfeeb8b 100644
--- a/worker/scripts/ansible_playbook
+++ b/worker/scripts/ansible_playbook
@@ -0,0 +1,67 @@
+#!/usr/bin/env -S deno run --allow-env --allow-net --allow-run --allow-read --allow-write
+
+import {
+ BitwardenSession,
+ getRequiredEnv,
+ getStdout,
+ type SecureNote,
+} from "@liz-ci/utils";
+import type { AnsiblePlaybookJobProps } from "@liz-ci/model";
+
+const args: AnsiblePlaybookJobProps = {
+ path: getRequiredEnv("path"),
+ playbooks: getRequiredEnv("playbooks"),
+};
+
+const tempKeyFile = await Deno.makeTempFile();
+const cwd = Deno.cwd();
+const bitwardenSession = new BitwardenSession();
+
+try {
+ Deno.chdir(args.path);
+
+ const { notes: ansibleSecrets } = await bitwardenSession.getItem<SecureNote>(
+ "ansible_secrets",
+ );
+ await Deno.writeTextFile("secrets.yml", ansibleSecrets);
+
+ const { notes: privateKey } = await bitwardenSession.getItem<SecureNote>(
+ "ssh_key",
+ );
+
+ // Create a temporary file for the SSH key
+ await Deno.writeTextFile(tempKeyFile, privateKey);
+ await getStdout(["chmod", "600", tempKeyFile]);
+
+ // Start ssh-agent and add the key
+ const sshAgent = await getStdout(["ssh-agent", "-s"]);
+ const [SSH_AGENT_PID, SSH_AUTH_SOCK] = [
+ /SSH_AGENT_PID=(\d+)/,
+ /SSH_AUTH_SOCK=([^;]+)/,
+ ]
+ .map((regex) => sshAgent.match(regex)?.[1])
+ .map((val) => {
+ if (!val) throw new Error("Failed to start ssh-agent");
+ return val;
+ });
+
+ const sshEnv = {
+ SSH_AGENT_PID,
+ SSH_AUTH_SOCK,
+ };
+ await getStdout(["ssh-add", tempKeyFile], {
+ env: sshEnv,
+ });
+ await getStdout([
+ "ansible-playbook",
+ "-e",
+ "@secrets.yml",
+ ...args.playbooks.split(" "),
+ ], { env: sshEnv });
+} finally {
+ await Promise.allSettled([
+ Deno.chdir.bind(null, cwd),
+ Deno.remove(tempKeyFile),
+ getStdout(["ssh-agent", "-k"]),
+ ]);
+}