A dependency-free node20 action that triggers application.deploy or compose.deploy, then follows the deployment to a terminal status so a failed build fails the CI run. Snapshots the deployment list before triggering, so a concurrent deployment is never mistaken for this one. Optionally points the application at a freshly built image first, which is what lets a workflow lint, test, push and deploy the same artifact.
167 lines
5.4 KiB
JavaScript
167 lines
5.4 KiB
JavaScript
"use strict";
|
|
|
|
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
|
|
const { FakeApi } = require("./helpers");
|
|
const { resolveTarget } = require("../src/resolve");
|
|
|
|
const PROJECTS = [
|
|
{
|
|
projectId: "p1",
|
|
name: "shop",
|
|
environments: [
|
|
{
|
|
environmentId: "e1",
|
|
name: "production",
|
|
isDefault: true,
|
|
applications: [{ applicationId: "a1", name: "api", appName: "shop-api-prod" }],
|
|
compose: [],
|
|
},
|
|
{
|
|
environmentId: "e2",
|
|
name: "staging",
|
|
isDefault: false,
|
|
applications: [{ applicationId: "a2", name: "api", appName: "shop-api-stage" }],
|
|
compose: [{ composeId: "c1", name: "worker", appName: "shop-worker" }],
|
|
},
|
|
],
|
|
},
|
|
{ projectId: "p2", name: "blog", environments: [] },
|
|
];
|
|
|
|
function apiWithProjects(projects = PROJECTS) {
|
|
return new FakeApi({ "project.all": projects });
|
|
}
|
|
|
|
test("an application id is confirmed through application.one", async () => {
|
|
const api = new FakeApi({
|
|
"application.one": { name: "api", appName: "shop-api-prod", environmentId: "e1" },
|
|
});
|
|
|
|
const target = await resolveTarget(api, { applicationId: "a1" });
|
|
|
|
assert.deepEqual(target, { kind: "application", id: "a1", name: "api", appName: "shop-api-prod", environmentId: "e1" });
|
|
assert.deepEqual(api.find("application.one").input, { applicationId: "a1" });
|
|
});
|
|
|
|
test("a compose id is confirmed through compose.one", async () => {
|
|
const api = new FakeApi({ "compose.one": { name: "worker", appName: "shop-worker", environmentId: "e2" } });
|
|
|
|
const target = await resolveTarget(api, { composeId: "c1" });
|
|
|
|
assert.equal(target.kind, "compose");
|
|
assert.equal(target.id, "c1");
|
|
});
|
|
|
|
test("a name resolves against the project's default environment", async () => {
|
|
const api = apiWithProjects();
|
|
|
|
const target = await resolveTarget(api, { project: "shop", service: "api" });
|
|
|
|
assert.equal(target.id, "a1");
|
|
assert.equal(target.environmentId, "e1");
|
|
assert.equal(target.environmentName, "production");
|
|
assert.equal(target.appName, "shop-api-prod");
|
|
});
|
|
|
|
test("an explicit environment picks the other copy of the same name", async () => {
|
|
const api = apiWithProjects();
|
|
|
|
const target = await resolveTarget(api, { project: "shop", service: "api", environment: "staging" });
|
|
|
|
assert.equal(target.id, "a2");
|
|
assert.equal(target.environmentName, "staging");
|
|
});
|
|
|
|
test("a Compose stack resolves by name", async () => {
|
|
const api = apiWithProjects();
|
|
|
|
const target = await resolveTarget(api, { project: "shop", service: "worker", environment: "staging" });
|
|
|
|
assert.equal(target.kind, "compose");
|
|
assert.equal(target.id, "c1");
|
|
});
|
|
|
|
test("the Docker service name works as well as the display name", async () => {
|
|
const api = apiWithProjects();
|
|
|
|
const target = await resolveTarget(api, { project: "shop", service: "shop-api-prod" });
|
|
|
|
assert.equal(target.id, "a1");
|
|
});
|
|
|
|
test("names match case-insensitively", async () => {
|
|
const api = apiWithProjects();
|
|
|
|
const target = await resolveTarget(api, { project: "SHOP", service: "API" });
|
|
|
|
assert.equal(target.id, "a1");
|
|
});
|
|
|
|
test("an unknown project lists the ones that exist", async () => {
|
|
const api = apiWithProjects();
|
|
|
|
await assert.rejects(
|
|
() => resolveTarget(api, { project: "store", service: "api" }),
|
|
/No project named "store"\. Available: blog, shop\./,
|
|
);
|
|
});
|
|
|
|
test("an unknown environment lists the ones that exist", async () => {
|
|
const api = apiWithProjects();
|
|
|
|
await assert.rejects(
|
|
() => resolveTarget(api, { project: "shop", service: "api", environment: "qa" }),
|
|
/no environment named "qa"\. Available: production, staging\./,
|
|
);
|
|
});
|
|
|
|
test("an unknown service lists the ones in that environment", async () => {
|
|
const api = apiWithProjects();
|
|
|
|
await assert.rejects(
|
|
() => resolveTarget(api, { project: "shop", service: "cron", environment: "staging" }),
|
|
/No application or Compose stack named "cron" in shop\/staging\. Available: api, worker\./,
|
|
);
|
|
});
|
|
|
|
test("a name shared by an application and a Compose stack asks for an id", async () => {
|
|
const api = apiWithProjects([
|
|
{
|
|
projectId: "p1",
|
|
name: "shop",
|
|
environments: [
|
|
{
|
|
environmentId: "e1",
|
|
name: "production",
|
|
isDefault: true,
|
|
applications: [{ applicationId: "a1", name: "api", appName: "shop-api" }],
|
|
compose: [{ composeId: "c1", name: "api", appName: "shop-api-compose" }],
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
|
|
await assert.rejects(
|
|
() => resolveTarget(api, { project: "shop", service: "api" }),
|
|
/ambiguous in shop\/production \(application and compose\)\. Use `application-id` or `compose-id`/,
|
|
);
|
|
});
|
|
|
|
test("a project with no environments is reported as such", async () => {
|
|
const api = apiWithProjects();
|
|
|
|
await assert.rejects(() => resolveTarget(api, { project: "blog", service: "api" }), /has no environments/);
|
|
});
|
|
|
|
test("conflicting or missing target inputs are rejected before any request", async () => {
|
|
const api = new FakeApi({});
|
|
|
|
await assert.rejects(() => resolveTarget(api, { applicationId: "a1", composeId: "c1" }), /not both/);
|
|
await assert.rejects(() => resolveTarget(api, { applicationId: "a1", project: "shop", service: "api" }), /not both/);
|
|
await assert.rejects(() => resolveTarget(api, {}), /No target/);
|
|
await assert.rejects(() => resolveTarget(api, { project: "shop" }), /No target/);
|
|
assert.equal(api.calls.length, 0);
|
|
});
|