/** * Global setup: source live credentials from the cluster once, before any test * runs, and fail loudly if a prerequisite is missing. * * The suite must not silently skip a check because a credential was absent — * that is the "false green" failure mode this whole harness exists to prevent. * Every prerequisite either resolves here or the run stops with an actionable * message. */ import { kubectl, secretValue } from './cluster'; import { BACKUP_NAMESPACE, NAMESPACE, OBS_NAMESPACE, TIDALCTL_BIN, } from './env'; import { access } from 'node:fs/promises'; type Prerequisite = { name: string; check: () => Promise }; async function firstFailure(prerequisites: Prerequisite[]): Promise { const failures: string[] = []; for (const prerequisite of prerequisites) { const problem = await prerequisite.check(); if (problem) failures.push(` ✗ ${prerequisite.name}: ${problem}`); } return failures; } export default async function globalSetup(): Promise { const failures = await firstFailure([ { name: 'kubectl reaches the cluster', check: async () => { const result = await kubectl(['get', '--raw', '/readyz'], { timeoutMs: 15_000 }); if (result.code !== 0) return `kubectl not usable — ${result.stderr.trim()}`; return undefined; }, }, { name: `namespace ${NAMESPACE} exists`, check: async () => { const result = await kubectl(['get', 'namespace', NAMESPACE, '-o', 'name']); return result.code === 0 ? undefined : result.stderr.trim(); }, }, { name: `namespace ${OBS_NAMESPACE} exists`, check: async () => { const result = await kubectl(['get', 'namespace', OBS_NAMESPACE, '-o', 'name']); return result.code === 0 ? undefined : result.stderr.trim(); }, }, { name: `namespace ${BACKUP_NAMESPACE} exists`, check: async () => { const result = await kubectl(['get', 'namespace', BACKUP_NAMESPACE, '-o', 'name']); return result.code === 0 ? undefined : result.stderr.trim(); }, }, { name: `${TIDALCTL_BIN} is built`, check: async () => { try { await access(TIDALCTL_BIN); return undefined; } catch { return `not found — run 'cargo build -p tidalctl'`; } }, }, ]); if (failures.length > 0) { throw new Error( `Deploy-verification prerequisites failed:\n${failures.join('\n')}\n\n` + `See docs/runbooks/deploy-verification.md section 0.`, ); } // Source credentials from the cluster so no operator has to paste a secret // into their shell. Written into process.env for the workers to inherit. if (!process.env.E2E_TIDAL_API_KEY) { const key = await secretValue(NAMESPACE, 'tidaldb-credentials', 'TIDAL_API_KEY'); if (!key) { throw new Error( `Could not read TIDAL_API_KEY from secret ${NAMESPACE}/tidaldb-credentials. ` + `The data-plane bearer is required for every auth check.`, ); } process.env.E2E_TIDAL_API_KEY = key; } // Optional: present only after the admin-key image is rolled (section 9.2). if (!process.env.E2E_TIDAL_ADMIN_KEY) { const adminKey = await secretValue( NAMESPACE, 'tidaldb-credentials', 'TIDAL_ADMIN_KEY', ); if (adminKey) process.env.E2E_TIDAL_ADMIN_KEY = adminKey; } if (!process.env.E2E_GRAFANA_PASSWORD) { const password = await secretValue(OBS_NAMESPACE, 'grafana-admin', 'password'); if (!password) { throw new Error( `Could not read password from secret ${OBS_NAMESPACE}/grafana-admin. ` + `The dashboard checks in section 5 need it.`, ); } process.env.E2E_GRAFANA_PASSWORD = password; } // Record the image actually running, so every artifact says what was verified // rather than what the repo happened to contain. if (!process.env.E2E_BUILD_REVISION) { const image = await kubectl([ '-n', NAMESPACE, 'get', 'statefulset', 'tidaldb', '-o', 'jsonpath={.spec.template.spec.containers[0].image}', ]); process.env.E2E_BUILD_REVISION = image.code === 0 && image.stdout.trim() !== '' ? image.stdout.trim() : 'unknown'; } if (!process.env.E2E_RUN_ID) { process.env.E2E_RUN_ID = `verify-${new Date().toISOString().replace(/[:.]/g, '-')}`; } process.stdout.write( `\ndeploy-verification prerequisites OK\n` + ` image: ${process.env.E2E_BUILD_REVISION}\n` + ` run-id: ${process.env.E2E_RUN_ID}\n` + ` admin key present: ${process.env.E2E_TIDAL_ADMIN_KEY ? 'yes' : 'no (pre-roll, section 9.2)'}\n\n`, ); }