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-green">Exists</span>'
|
||||||
: '<span class="badge badge-red">Not found</span>'}</span>
|
: '<span class="badge badge-red">Not found</span>'}</span>
|
||||||
</div>
|
</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">
|
<div class="stat-row">
|
||||||
<span class="stat-label">Storage</span>
|
<span class="stat-label">Storage</span>
|
||||||
<span class="stat-value">${infra?.spaces?.configured
|
<span class="stat-value">${infra?.spaces?.configured
|
||||||
|
|||||||
@@ -113,6 +113,17 @@ export async function getConfigMap(namespace: string, name: string): Promise<Rec
|
|||||||
return cm.data ?? {};
|
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) {
|
export async function rolloutRestart(namespace: string, deployment: string) {
|
||||||
return (await k8sFetch(`/apis/apps/v1/namespaces/${namespace}/deployments/${deployment}`, {
|
return (await k8sFetch(`/apis/apps/v1/namespaces/${namespace}/deployments/${deployment}`, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { createDatabase, createDatabaseUser, deleteDatabase, deleteDatabaseUser
|
|||||||
import { addCustomerToPool, removeCustomerFromPool } from "../services/pgbouncer";
|
import { addCustomerToPool, removeCustomerFromPool } from "../services/pgbouncer";
|
||||||
import { addCustomerChart, removeCustomerChart, upgradeCustomerChart, upgradeAllCustomerCharts, getLatestChartVersion } from "../services/git";
|
import { addCustomerChart, removeCustomerChart, upgradeCustomerChart, upgradeAllCustomerCharts, getLatestChartVersion } from "../services/git";
|
||||||
import { setupCustomerDatabase, teardownCustomerDatabase } from "../services/db";
|
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 { deleteSpacesObjects, getSpacesUsage } from "../services/spaces";
|
||||||
import { createCustomerDnsRecord, deleteCustomerDnsRecord, getCustomerDnsRecord, checkCustomerHealth } from "../services/cloudflare";
|
import { createCustomerDnsRecord, deleteCustomerDnsRecord, getCustomerDnsRecord, checkCustomerHealth } from "../services/cloudflare";
|
||||||
import { db } from "../db/manager";
|
import { db } from "../db/manager";
|
||||||
@@ -229,8 +229,8 @@ export async function customerRoutes(app: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Infrastructure checks ─────────────────────────────────────────────────
|
// ── Infrastructure checks ─────────────────────────────────────────────────
|
||||||
const [dbCheck, sizeHistory, secrets, dnsCheck, healthCheck] = await Promise.allSettled([
|
const [dbCheck, pgbouncerCheck, sizeHistory, secrets, dnsCheck, healthCheck] = await Promise.allSettled([
|
||||||
// Try connecting to the customer DB
|
// Try connecting to the customer DB directly
|
||||||
(async () => {
|
(async () => {
|
||||||
const sql = postgres(config.doadminDbUrl.replace(/\/([^/?]+)(\?|$)/, `/${slug}$2`), { max: 1, connect_timeout: 5 });
|
const sql = postgres(config.doadminDbUrl.replace(/\/([^/?]+)(\?|$)/, `/${slug}$2`), { max: 1, connect_timeout: 5 });
|
||||||
try {
|
try {
|
||||||
@@ -240,6 +240,16 @@ export async function customerRoutes(app: FastifyInstance) {
|
|||||||
await sql.end();
|
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`
|
db`
|
||||||
SELECT recorded_at, db_size_bytes, spaces_size_bytes, spaces_object_count
|
SELECT recorded_at, db_size_bytes, spaces_size_bytes, spaces_object_count
|
||||||
FROM customer_size_snapshots
|
FROM customer_size_snapshots
|
||||||
@@ -253,11 +263,13 @@ export async function customerRoutes(app: FastifyInstance) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const dbExists = dbCheck.status === "fulfilled" ? dbCheck.value : false;
|
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 secretData = secrets.status === "fulfilled" ? secrets.value : null;
|
||||||
const dns = dnsCheck.status === "fulfilled" ? dnsCheck.value : { exists: false, proxied: false, ip: 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 health = healthCheck.status === "fulfilled" ? healthCheck.value : { reachable: false, status: null };
|
||||||
const infra = {
|
const infra = {
|
||||||
database: { exists: dbExists },
|
database: { exists: dbExists },
|
||||||
|
pgbouncer: { configured: pgbouncerReachable },
|
||||||
spaces: {
|
spaces: {
|
||||||
configured: !!(secretData?.["spaces-prefix"]),
|
configured: !!(secretData?.["spaces-prefix"]),
|
||||||
bucket: secretData?.["spaces-bucket"] ?? null,
|
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" });
|
if (!customer) return reply.code(404).send({ message: "Not found" });
|
||||||
const version = await getLatestChartVersion();
|
const version = await getLatestChartVersion();
|
||||||
await upgradeCustomerChart(slug, version);
|
await upgradeCustomerChart(slug, version);
|
||||||
|
await syncArgoApp(`customer-${slug}`);
|
||||||
await db`UPDATE customers SET updated_at = NOW() WHERE slug = ${slug}`;
|
await db`UPDATE customers SET updated_at = NOW() WHERE slug = ${slug}`;
|
||||||
app.log.info({ slug, version }, "customer chart upgraded");
|
app.log.info({ slug, version }, "customer chart upgraded");
|
||||||
return reply.send({ slug, version });
|
return reply.send({ slug, version });
|
||||||
@@ -322,6 +335,7 @@ export async function customerRoutes(app: FastifyInstance) {
|
|||||||
const version = await getLatestChartVersion();
|
const version = await getLatestChartVersion();
|
||||||
const slugs = customers.map((c: any) => c.slug);
|
const slugs = customers.map((c: any) => c.slug);
|
||||||
await upgradeAllCustomerCharts(slugs, version);
|
await upgradeAllCustomerCharts(slugs, version);
|
||||||
|
await Promise.all(slugs.map((s: string) => syncArgoApp(`customer-${s}`)));
|
||||||
app.log.info({ slugs, version }, "all customers chart upgraded");
|
app.log.info({ slugs, version }, "all customers chart upgraded");
|
||||||
return reply.send({ upgraded: slugs, version });
|
return reply.send({ upgraded: slugs, version });
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user