Publish the native release as a container image and document deployment
A static nginx image (unprivileged, read-only) of the production build, published to Gitea's registry for amd64 and arm64: version tags go to prod, manual dev-<commit> builds to dev, each smoke-tested first and optionally announced to Dockhand. The README describes running it with Docker Compose. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d3e3faff72
commit
b33725adfa
9 files changed
+584
No files matched your search
@@ -0,0 +1,115 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createHmac } from "node:crypto";
|
||||
import { imagePublication } from "../scripts/image-publication.mjs";
|
||||
import { notifyDockhand } from "../scripts/notify-dockhand.mjs";
|
||||
|
||||
const revision = "abcdef01".repeat(5);
|
||||
const publish = (event: string, ref: string, inputTag = "") =>
|
||||
imagePublication({ event, ref, inputTag, revision });
|
||||
|
||||
describe("image publication channels", () => {
|
||||
it.each(["v1.2.3", "1.2.3", "v0.0.0", "v10.20.300"])(
|
||||
"publishes stable version %s to prod",
|
||||
(tag) =>
|
||||
expect(publish("push", `refs/tags/${tag}`)).toEqual({
|
||||
tag,
|
||||
channel: "prod",
|
||||
revision,
|
||||
}),
|
||||
);
|
||||
|
||||
it.each(["abcdef0", revision])(
|
||||
"publishes a matching manual commit %s only to dev",
|
||||
(commit) => {
|
||||
expect(
|
||||
publish("workflow_dispatch", "refs/heads/main", `dev-${commit}`),
|
||||
).toEqual({ tag: `dev-${commit}`, channel: "dev", revision });
|
||||
expect(
|
||||
publish("workflow_dispatch", "refs/tags/v1.2.3", `dev-${commit}`)
|
||||
.channel,
|
||||
).toBe("dev");
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
"v1.2.3-rc.1",
|
||||
"v1.2.3+build",
|
||||
"v01.2.3",
|
||||
"v1.2",
|
||||
"prod",
|
||||
"latest",
|
||||
"dev-abcdef0",
|
||||
"release/v1.2.3",
|
||||
"v1.2.3\nchannel=prod",
|
||||
"v1.2.3\n",
|
||||
])("rejects non-release Git tag %s before publication", (tag) =>
|
||||
expect(() => publish("push", `refs/tags/${tag}`)).toThrow(),
|
||||
);
|
||||
|
||||
it.each([
|
||||
"v1.2.3",
|
||||
"prod",
|
||||
"latest",
|
||||
"dev",
|
||||
"",
|
||||
"dev-1234567",
|
||||
"dev-abcdef",
|
||||
"dev-ABCDEF0",
|
||||
"dev-abcdef0\nchannel=prod",
|
||||
"dev-abcdef0\n",
|
||||
"dev-$(exit 1)",
|
||||
])("rejects manual tag %s instead of promoting it", (tag) =>
|
||||
expect(() =>
|
||||
publish("workflow_dispatch", "refs/heads/main", tag),
|
||||
).toThrow(),
|
||||
);
|
||||
|
||||
it("does not publish branches or pull requests", () => {
|
||||
expect(() => publish("push", "refs/heads/main")).toThrow();
|
||||
expect(() => publish("pull_request", "refs/tags/v1.2.3")).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("optional Dockhand deployment", () => {
|
||||
const url = "https://dockhand.example.com/api/git/stacks/42/webhook";
|
||||
const secret = "test-webhook-secret";
|
||||
|
||||
it("does not contact Dockhand when this channel has no configuration", async () => {
|
||||
const send = vi.fn();
|
||||
await expect(notifyDockhand({}, send)).resolves.toBe(false);
|
||||
expect(send).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([{ url }, { secret }])(
|
||||
"rejects incomplete configuration",
|
||||
async (config) => {
|
||||
const send = vi.fn();
|
||||
await expect(notifyDockhand(config, send)).rejects.toThrow();
|
||||
expect(send).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it("signs exactly the posted payload and refuses redirects", async () => {
|
||||
const send = vi.fn().mockResolvedValue(new Response("{}"));
|
||||
await expect(notifyDockhand({ url, secret }, send)).resolves.toBe(true);
|
||||
const [target, request] = send.mock.calls[0];
|
||||
expect(target.href).toBe(url);
|
||||
expect(request.method).toBe("POST");
|
||||
expect(request.body).toBe("{}");
|
||||
expect(request.redirect).toBe("error");
|
||||
expect(request.headers["X-Hub-Signature-256"]).toBe(
|
||||
`sha256=${createHmac("sha256", secret).update(request.body).digest("hex")}`,
|
||||
);
|
||||
});
|
||||
|
||||
it("reports failed deployment requests", async () => {
|
||||
const send = vi
|
||||
.fn()
|
||||
.mockResolvedValue(
|
||||
new Response("private server details", { status: 403 }),
|
||||
);
|
||||
await expect(notifyDockhand({ url, secret }, send)).rejects.toThrow(
|
||||
"HTTP 403",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user