feat: improve API keys and build workflows

This commit is contained in:
2026-10-04 20:05:13 +00:00 Unverified
parent 62f2362a2e
commit e4623efe86
27 changed files with 2080 additions and 235 deletions
+70 -42
View File
@@ -4,6 +4,27 @@ import type { ComposeSpecification, Service } from "../schema/docker.d";
import { LABELS } from "../const";
import { deleteResource, applyResource } from "./apply";
export class DatabaseReconciliationError extends Error {
constructor(phase: string, error: unknown, claim?: PostgresClaim) {
const context = claim
? ` for database ${claim.database} (service ${claim.service}, role ${claim.username})`
: "";
const reason = error instanceof Error ? error.message : String(error);
// Provider errors can contain credentials or entire request bodies. Only
// expose a short, recognisable operational reason, never a raw response.
const knownReason = /^(forbidden|not found|conflict|permission denied|connection refused|timed out|timeout|unauthorized|unprocessable entity|service unavailable)\b/i.exec(reason);
const status = error && typeof error === "object" && "code" in error &&
typeof error.code === "number" && error.code >= 400 && error.code < 600
? ` (HTTP ${error.code})`
: "";
const safeReason = `${knownReason ? knownReason[1] : "Kubernetes request failed"}${status}`;
super(`Database reconciliation failed during ${phase}${context}: ${safeReason}`, {
cause: error,
});
this.name = "DatabaseReconciliationError";
}
}
export const DATABASE_NAMESPACE = "database";
export const DATABASE_CLUSTER = "postgres";
export const DATABASE_HOST = `c.${DATABASE_NAMESPACE}.svc.cluster.local`;
@@ -189,33 +210,37 @@ async function readObject<T>(
async function ensureRoleSecret(
claim: PostgresClaim,
): Promise<RoleCredentials> {
const existing = await readObject<V1Secret>({
apiVersion: "v1",
kind: "Secret",
metadata: {
name: claim.secretName,
namespace: DATABASE_NAMESPACE,
},
});
try {
const existing = await readObject<V1Secret>({
apiVersion: "v1",
kind: "Secret",
metadata: {
name: claim.secretName,
namespace: DATABASE_NAMESPACE,
},
});
const username = claim.username;
const password = decodeSecretValue(existing?.data?.password) ?? randomUUID();
const username = claim.username;
const password = decodeSecretValue(existing?.data?.password) ?? randomUUID();
await applyResource({
apiVersion: "v1",
kind: "Secret",
metadata: {
name: claim.secretName,
namespace: DATABASE_NAMESPACE,
},
type: existing?.type ?? "Opaque",
stringData: {
username,
password,
},
} satisfies V1Secret);
await applyResource({
apiVersion: "v1",
kind: "Secret",
metadata: {
name: claim.secretName,
namespace: DATABASE_NAMESPACE,
},
type: existing?.type ?? "Opaque",
stringData: {
username,
password,
},
} satisfies V1Secret);
return { username, password };
return { username, password };
} catch (error) {
throw new DatabaseReconciliationError("role secret setup", error, claim);
}
}
function toManagedRole(claim: PostgresClaim): ManagedRole {
@@ -247,20 +272,27 @@ async function reconcileManagedRoles(
): Promise<void> {
if (claims.length === 0) return;
const cluster = await readObject<
KubernetesObject & { spec?: { managed?: { roles?: ManagedRole[] } } }
>({
apiVersion: "postgresql.cnpg.io/v1",
kind: "Cluster",
metadata: {
name: DATABASE_CLUSTER,
namespace: DATABASE_NAMESPACE,
},
});
let cluster: KubernetesObject & { spec?: { managed?: { roles?: ManagedRole[] } } } | undefined;
try {
cluster = await readObject<
KubernetesObject & { spec?: { managed?: { roles?: ManagedRole[] } } }
>({
apiVersion: "postgresql.cnpg.io/v1",
kind: "Cluster",
metadata: {
name: DATABASE_CLUSTER,
namespace: DATABASE_NAMESPACE,
},
});
} catch (error) {
throw new DatabaseReconciliationError("cluster lookup", error, claims[0]);
}
if (!cluster) {
throw new Error(
`CNPG cluster ${DATABASE_CLUSTER} was not found in namespace ${DATABASE_NAMESPACE}.`,
throw new DatabaseReconciliationError(
`CNPG cluster ${DATABASE_NAMESPACE}/${DATABASE_CLUSTER} lookup`,
new Error("Not found"),
claims[0],
);
}
@@ -287,9 +319,7 @@ async function reconcileManagedRoles(
},
});
} catch (error) {
throw new Error(
`Failed to reconcile managed roles on ${DATABASE_NAMESPACE}/${DATABASE_CLUSTER}: ${error instanceof Error ? error.message : String(error)}`,
);
throw new DatabaseReconciliationError("managed role update", error, claims[0]);
}
}
@@ -329,9 +359,7 @@ async function reconcileDatabases(
},
});
} catch (error) {
throw new Error(
`Failed to reconcile database ${claim.database} owned by ${claim.username}: ${error instanceof Error ? error.message : String(error)}`,
);
throw new DatabaseReconciliationError("database apply", error, claim);
}
}
}