feat: improve deployment workflows

This commit is contained in:
2026-08-29 06:38:37 +00:00 Unverified
parent a1705487f2
commit 6ab5123f00
23 changed files with 1392 additions and 111 deletions
+2 -2
View File
@@ -45,11 +45,11 @@ export const down = defineCommand({
});
}
if (resources.length === 0) return;
await new Listr([
{
title: "Delete resources",
skip: () =>
resources.length > 0 ? false : "No managed resources to delete",
task: (_ctx, task) => {
const ordered = sortResources(resources).reverse();
+39
View File
@@ -0,0 +1,39 @@
import tab from "@bomb.sh/tab/citty";
import { defineCommand } from "citty";
import { db } from "./db";
import { down } from "./down";
import { exec } from "./exec";
import { exportCommand } from "./export";
import { logs } from "./logs";
import { ps } from "./ps";
import { restart } from "./restart";
import { start } from "./start";
import { stop } from "./stop";
import { up } from "./up";
export const main = defineCommand({
meta: {
name: "kuber",
version: "1.0.0",
description: "Docker Compose -> K8s translation layer",
},
subCommands: {
db,
down,
export: exportCommand,
exec,
logs,
ps,
restart,
start,
stop,
up,
},
});
let completion: ReturnType<typeof tab> | undefined;
export function initializeCompletion() {
completion ??= tab(main);
return completion;
}
+1 -2
View File
@@ -15,12 +15,11 @@ export const restart = defineCommand({
async run() {
await assertManagedNamespace(getProject());
const deployments = await listManagedDeployments();
if (deployments.length === 0) return;
await new Listr([
{
title: "Restart deployments",
skip: () =>
deployments.length > 0 ? false : "No managed deployments found",
task: (_ctx, task) => {
task.output = `${deployments.length} deployments queued`;
return task.newListr(
+6 -3
View File
@@ -1,7 +1,11 @@
import { defineCommand } from "citty";
import { Listr } from "listr2";
import { assertManagedNamespace } from "../lib/apply";
import { getProject, listManagedDeployments, scaleDeployment } from "../lib/shared";
import {
getProject,
listManagedDeployments,
scaleDeployment,
} from "../lib/shared";
export const stop = defineCommand({
meta: {
@@ -11,12 +15,11 @@ export const stop = defineCommand({
async run() {
await assertManagedNamespace(getProject());
const deployments = await listManagedDeployments();
if (deployments.length === 0) return;
await new Listr([
{
title: "Scale deployments",
skip: () =>
deployments.length > 0 ? false : "No managed deployments found",
task: (_ctx, task) => {
task.output = `${deployments.length} deployments queued`;
return task.newListr(
+41 -38
View File
@@ -20,12 +20,13 @@ import { restartDeployment, waitForDeploymentRollout } from "../lib/shared";
type UpContext = {
compose?: ComposeSpecification;
changedDeployments?: string[];
serviceEnv?: Record<string, Record<string, string>>;
resources?: KubernetesResource[];
staleResources?: KubernetesResource[];
};
function mergeServiceEnv(
export function mergeServiceEnv(
current: Record<string, Record<string, string>> | undefined,
next: Record<string, Record<string, string>>,
): Record<string, Record<string, string>> {
@@ -36,7 +37,7 @@ function mergeServiceEnv(
return merged;
}
function getDeploymentNames(resources: KubernetesResource[]): string[] {
export function getDeploymentNames(resources: KubernetesResource[]): string[] {
return resources
.filter((resource) => resource.kind === "Deployment")
.map((resource) => resource.metadata?.name)
@@ -47,7 +48,7 @@ export async function runUp(build: boolean) {
const { project, compose, cwd } = ctx();
await assertManagedNamespace(project);
await new Listr<UpContext>(
const taskCtx = await new Listr<UpContext>(
[
{
title: "Read compose",
@@ -62,29 +63,26 @@ export async function runUp(build: boolean) {
outputBar: 10,
persistentOutput: true,
},
enabled: () => build,
skip: (taskCtx) =>
Object.values(taskCtx.compose?.services ?? {}).some(
enabled: async () =>
build &&
Object.values((await compose()).services ?? {}).some(
(service) => service.build,
)
? false
: "No buildable services",
),
task: async (taskCtx, task) => {
const built = await buildServices(project, taskCtx.compose!, cwd, {
const result = await buildServices(project, taskCtx.compose!, cwd, {
progress: (message) => {
task.output = message;
},
stream: task.stdout(),
});
task.output = `Built ${built} image${built === 1 ? "" : "s"}`;
taskCtx.changedDeployments = result.changed;
task.output = `Built ${result.built.length} image${result.built.length === 1 ? "" : "s"}, ${result.changed.length} changed`;
},
},
{
title: "Reconcile databases",
skip: (taskCtx) =>
getComposePostgresClaims(taskCtx.compose!).length > 0
? false
: "No managed postgres volumes",
enabled: async () =>
getComposePostgresClaims(await compose()).length > 0,
task: async (taskCtx, task) => {
taskCtx.serviceEnv = mergeServiceEnv(
taskCtx.serviceEnv,
@@ -95,10 +93,7 @@ export async function runUp(build: boolean) {
},
{
title: "Reconcile S3 storage",
skip: (taskCtx) =>
getComposeS3Claims(taskCtx.compose!).length > 0
? false
: "No managed S3 volumes",
enabled: async () => getComposeS3Claims(await compose()).length > 0,
task: async (taskCtx, task) => {
taskCtx.serviceEnv = mergeServiceEnv(
taskCtx.serviceEnv,
@@ -144,14 +139,19 @@ export async function runUp(build: boolean) {
);
},
},
{
title: "Restart deployments",
task: (taskCtx, task) => {
const deployments = getDeploymentNames(taskCtx.resources!);
],
{ rendererOptions: { collapseErrors: false } },
).run();
task.output = `${deployments.length} deployments queued`;
const changedDeployments = taskCtx.changedDeployments ?? [];
if (changedDeployments.length > 0) {
await new Listr([
{
title: "Restart changed deployments",
task: (_taskCtx, task) => {
task.output = `${changedDeployments.length} deployments queued`;
return task.newListr(
deployments.map((name) => ({
changedDeployments.map((name) => ({
title: `Deployment ${name}`,
task: () => restartDeployment(name),
})),
@@ -159,11 +159,15 @@ export async function runUp(build: boolean) {
);
},
},
]).run();
}
const deployments = getDeploymentNames(taskCtx.resources!);
if (deployments.length > 0) {
await new Listr([
{
title: "Wait for rollout",
task: (taskCtx, task) => {
const deployments = getDeploymentNames(taskCtx.resources!);
task: (_taskCtx, task) => {
task.output = `${deployments.length} deployments queued`;
return task.newListr(
deployments.map((name) => ({
@@ -174,15 +178,15 @@ export async function runUp(build: boolean) {
);
},
},
]).run();
}
if (taskCtx.staleResources && taskCtx.staleResources.length > 0) {
const resources = sortResources(taskCtx.staleResources).reverse();
await new Listr([
{
title: "Delete stale resources",
skip: (taskCtx) =>
taskCtx.staleResources && taskCtx.staleResources.length > 0
? false
: "No stale resources",
task: (taskCtx, task) => {
const resources = sortResources(taskCtx.staleResources!).reverse();
task: (_taskCtx, task) => {
task.output = `${resources.length} resources queued`;
return task.newListr(
resources.map((resource) => ({
@@ -193,9 +197,8 @@ export async function runUp(build: boolean) {
);
},
},
],
{ rendererOptions: { collapseErrors: false } },
).run();
]).run();
}
}
export const up = defineCommand({