feat: add pgbouncer check to overview, auto-sync ArgoCD after upgrade
Some checks failed
Build & Release / build (push) Has been cancelled

This commit is contained in:
Ryan Moon
2026-04-03 22:00:10 -05:00
parent 32a9368e9d
commit 8ec3b4d247
3 changed files with 34 additions and 3 deletions

View File

@@ -729,6 +729,12 @@
? '<span class="badge badge-green">Exists</span>'
: '<span class="badge badge-red">Not found</span>'}</span>
</div>
<div class="stat-row">
<span class="stat-label">PgBouncer</span>
<span class="stat-value">${infra?.pgbouncer?.configured
? '<span class="badge badge-green">Reachable</span>'
: '<span class="badge badge-red">Not configured</span>'}</span>
</div>
<div class="stat-row">
<span class="stat-label">Storage</span>
<span class="stat-value">${infra?.spaces?.configured

View File

@@ -113,6 +113,17 @@ export async function getConfigMap(namespace: string, name: string): Promise<Rec
return cm.data ?? {};
}
export async function syncArgoApp(name: string) {
await k8sFetch(`/apis/argoproj.io/v1alpha1/namespaces/argocd/applications/${name}`, {
method: "PATCH",
headers: { "Content-Type": "application/merge-patch+json" },
body: JSON.stringify({
metadata: { annotations: { "argocd.argoproj.io/refresh": "hard" } },
operation: { sync: { prune: true } },
}),
}, [404]);
}
export async function rolloutRestart(namespace: string, deployment: string) {
return (await k8sFetch(`/apis/apps/v1/namespaces/${namespace}/deployments/${deployment}`, {
method: "PATCH",

View File

@@ -6,7 +6,7 @@ import { createDatabase, createDatabaseUser, deleteDatabase, deleteDatabaseUser
import { addCustomerToPool, removeCustomerFromPool } from "../services/pgbouncer";
import { addCustomerChart, removeCustomerChart, upgradeCustomerChart, upgradeAllCustomerCharts, getLatestChartVersion } from "../services/git";
import { setupCustomerDatabase, teardownCustomerDatabase } from "../services/db";
import { createNamespace, deleteNamespace, createSecret, createDockerRegistrySecret, patchSecret, getSecret, k8sFetch } from "../lib/k8s";
import { createNamespace, deleteNamespace, createSecret, createDockerRegistrySecret, patchSecret, getSecret, k8sFetch, syncArgoApp } from "../lib/k8s";
import { deleteSpacesObjects, getSpacesUsage } from "../services/spaces";
import { createCustomerDnsRecord, deleteCustomerDnsRecord, getCustomerDnsRecord, checkCustomerHealth } from "../services/cloudflare";
import { db } from "../db/manager";
@@ -229,8 +229,8 @@ export async function customerRoutes(app: FastifyInstance) {
}
// ── Infrastructure checks ─────────────────────────────────────────────────
const [dbCheck, sizeHistory, secrets, dnsCheck, healthCheck] = await Promise.allSettled([
// Try connecting to the customer DB
const [dbCheck, pgbouncerCheck, sizeHistory, secrets, dnsCheck, healthCheck] = await Promise.allSettled([
// Try connecting to the customer DB directly
(async () => {
const sql = postgres(config.doadminDbUrl.replace(/\/([^/?]+)(\?|$)/, `/${slug}$2`), { max: 1, connect_timeout: 5 });
try {
@@ -240,6 +240,16 @@ export async function customerRoutes(app: FastifyInstance) {
await sql.end();
}
})(),
// Try connecting via pgbouncer
(async () => {
const sql = postgres(`postgresql://${slug}@${PGBOUNCER_HOST}:${PGBOUNCER_PORT}/${slug}`, { max: 1, connect_timeout: 5 });
try {
await sql`SELECT 1`;
return true;
} finally {
await sql.end();
}
})(),
db`
SELECT recorded_at, db_size_bytes, spaces_size_bytes, spaces_object_count
FROM customer_size_snapshots
@@ -253,11 +263,13 @@ export async function customerRoutes(app: FastifyInstance) {
]);
const dbExists = dbCheck.status === "fulfilled" ? dbCheck.value : false;
const pgbouncerReachable = pgbouncerCheck.status === "fulfilled" ? pgbouncerCheck.value : false;
const secretData = secrets.status === "fulfilled" ? secrets.value : null;
const dns = dnsCheck.status === "fulfilled" ? dnsCheck.value : { exists: false, proxied: false, ip: null };
const health = healthCheck.status === "fulfilled" ? healthCheck.value : { reachable: false, status: null };
const infra = {
database: { exists: dbExists },
pgbouncer: { configured: pgbouncerReachable },
spaces: {
configured: !!(secretData?.["spaces-prefix"]),
bucket: secretData?.["spaces-bucket"] ?? null,
@@ -311,6 +323,7 @@ export async function customerRoutes(app: FastifyInstance) {
if (!customer) return reply.code(404).send({ message: "Not found" });
const version = await getLatestChartVersion();
await upgradeCustomerChart(slug, version);
await syncArgoApp(`customer-${slug}`);
await db`UPDATE customers SET updated_at = NOW() WHERE slug = ${slug}`;
app.log.info({ slug, version }, "customer chart upgraded");
return reply.send({ slug, version });
@@ -322,6 +335,7 @@ export async function customerRoutes(app: FastifyInstance) {
const version = await getLatestChartVersion();
const slugs = customers.map((c: any) => c.slug);
await upgradeAllCustomerCharts(slugs, version);
await Promise.all(slugs.map((s: string) => syncArgoApp(`customer-${s}`)));
app.log.info({ slugs, version }, "all customers chart upgraded");
return reply.send({ upgraded: slugs, version });
});