feat: improve deployment workflows
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { db } from "../../command/db";
|
||||
import { down } from "../../command/down";
|
||||
import { exec } from "../../command/exec";
|
||||
import { exportCommand } from "../../command/export";
|
||||
import { logs } from "../../command/logs";
|
||||
import { initializeCompletion, main } from "../../command/main";
|
||||
import { ps } from "../../command/ps";
|
||||
import { restart } from "../../command/restart";
|
||||
import { start } from "../../command/start";
|
||||
import { stop } from "../../command/stop";
|
||||
import { up } from "../../command/up";
|
||||
|
||||
type Command = {
|
||||
meta?: { name?: string; description?: string };
|
||||
args?: Record<string, Record<string, unknown>>;
|
||||
subCommands?: Record<string, Command>;
|
||||
run?: unknown;
|
||||
};
|
||||
|
||||
describe("CLI command definitions", () => {
|
||||
const commands = [
|
||||
["up", "Create and start deployments", up],
|
||||
["start", "Start deployments without rebuilding images", start],
|
||||
["stop", "Scale managed deployments to zero", stop],
|
||||
["restart", "Roll out a restart for managed deployments", restart],
|
||||
["down", "Delete managed resources except ingress and PVCs", down],
|
||||
["ps", "List deployments", ps],
|
||||
["logs", "Show deployment logs", logs],
|
||||
["exec", "Execute a command inside a deployment pod", exec],
|
||||
["export", "Write rendered manifests to a YAML file", exportCommand],
|
||||
["db", "Inspect managed postgres databases", db],
|
||||
] as const;
|
||||
|
||||
test.each(commands)(
|
||||
"defines %s command metadata",
|
||||
(name, description, command) => {
|
||||
const definition = command as Command;
|
||||
expect(definition.meta).toEqual({ name, description });
|
||||
if (name !== "db") expect(typeof definition.run).toBe("function");
|
||||
},
|
||||
);
|
||||
|
||||
test("defines build, cleanup, listing, logging, and export flags", () => {
|
||||
expect((up as Command).args?.build).toMatchObject({
|
||||
type: "boolean",
|
||||
alias: "b",
|
||||
default: true,
|
||||
});
|
||||
expect((down as Command).args?.full).toMatchObject({
|
||||
type: "boolean",
|
||||
alias: "f",
|
||||
});
|
||||
expect((ps as Command).args?.all).toMatchObject({
|
||||
type: "boolean",
|
||||
alias: "a",
|
||||
});
|
||||
expect((logs as Command).args?.follow).toMatchObject({
|
||||
type: "boolean",
|
||||
alias: "f",
|
||||
});
|
||||
expect((exportCommand as Command).args?.output).toMatchObject({
|
||||
type: "string",
|
||||
alias: "o",
|
||||
});
|
||||
});
|
||||
|
||||
test("defines both database inspection subcommands", () => {
|
||||
const subCommands = (db as Command).subCommands;
|
||||
expect(Object.keys(subCommands ?? {}).sort()).toEqual(["creds", "ls"]);
|
||||
expect(subCommands?.ls?.meta?.name).toBe("ls");
|
||||
expect(subCommands?.creds?.meta?.name).toBe("creds");
|
||||
expect(typeof subCommands?.ls?.run).toBe("function");
|
||||
expect(typeof subCommands?.creds?.run).toBe("function");
|
||||
});
|
||||
|
||||
test("registers shell completion on the root command", async () => {
|
||||
await initializeCompletion();
|
||||
expect((await Promise.resolve(main.meta))?.name).toBe("kuber");
|
||||
expect(
|
||||
Object.keys((await Promise.resolve(main.subCommands)) ?? {}),
|
||||
).toContain("complete");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { getDeploymentNames, mergeServiceEnv } from "../../command/up";
|
||||
import { buildServices, imageDigestChanged } from "../../lib/build";
|
||||
import type { KubernetesResource } from "../../lib/convert";
|
||||
import type { ComposeSpecification } from "../../schema/docker.d";
|
||||
|
||||
describe("up task selection", () => {
|
||||
test("merges generated service environments without dropping providers", () => {
|
||||
expect(
|
||||
mergeServiceEnv(
|
||||
{
|
||||
app: { DATABASE_URL: "postgresql://database" },
|
||||
worker: { EXISTING: "yes" },
|
||||
},
|
||||
{
|
||||
app: { AWS_ACCESS_KEY_ID: "key" },
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
app: {
|
||||
DATABASE_URL: "postgresql://database",
|
||||
AWS_ACCESS_KEY_ID: "key",
|
||||
},
|
||||
worker: { EXISTING: "yes" },
|
||||
});
|
||||
});
|
||||
|
||||
test("selects only named Deployment resources for rollout waiting", () => {
|
||||
const resources = [
|
||||
{
|
||||
apiVersion: "apps/v1",
|
||||
kind: "Deployment",
|
||||
metadata: { name: "app" },
|
||||
},
|
||||
{
|
||||
apiVersion: "apps/v1",
|
||||
kind: "Deployment",
|
||||
metadata: {},
|
||||
},
|
||||
{ apiVersion: "v1", kind: "Service", metadata: { name: "app" } },
|
||||
] as KubernetesResource[];
|
||||
expect(getDeploymentNames(resources)).toEqual(["app"]);
|
||||
});
|
||||
|
||||
test("reports no built deployments without invoking build infrastructure", async () => {
|
||||
expect(
|
||||
await buildServices(
|
||||
"project",
|
||||
{
|
||||
services: { app: { image: "nginx:latest" } },
|
||||
} as ComposeSpecification,
|
||||
process.cwd(),
|
||||
),
|
||||
).toEqual({ built: [], changed: [] });
|
||||
});
|
||||
|
||||
test("restarts only for changed or unknown image digests", () => {
|
||||
const digest = `sha256:${"a".repeat(64)}`;
|
||||
expect(imageDigestChanged(digest, digest)).toBe(false);
|
||||
expect(imageDigestChanged(digest, `sha256:${"b".repeat(64)}`)).toBe(true);
|
||||
expect(imageDigestChanged(undefined, digest)).toBe(true);
|
||||
expect(imageDigestChanged(digest, undefined)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { db } from "../../command/db";
|
||||
import { exec } from "../../command/exec";
|
||||
|
||||
type RunnableCommand = {
|
||||
run?: (context: { args: { _: string[] } }) => Promise<unknown>;
|
||||
subCommands?: Record<string, RunnableCommand>;
|
||||
};
|
||||
|
||||
describe("CLI argument validation", () => {
|
||||
test("exec requires a deployment", async () => {
|
||||
await expect(
|
||||
(exec as RunnableCommand).run?.({ args: { _: [] } }),
|
||||
).rejects.toThrow("Deployment name is required");
|
||||
});
|
||||
|
||||
test("exec requires a command", async () => {
|
||||
await expect(
|
||||
(exec as RunnableCommand).run?.({ args: { _: ["app"] } }),
|
||||
).rejects.toThrow("Command is required");
|
||||
});
|
||||
|
||||
test("database credentials require a service", async () => {
|
||||
await expect(
|
||||
(db as RunnableCommand).subCommands?.creds?.run?.({ args: { _: [] } }),
|
||||
).rejects.toThrow("Service name is required");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user