feat: add pgbouncer check to overview, auto-sync ArgoCD after upgrade
Some checks failed
Build & Release / build (push) Has been cancelled
Some checks failed
Build & Release / build (push) Has been cancelled
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user