Pulse/tests/integration/scripts/hosted-tenant-runtime.mjs
2026-04-24 11:50:59 +01:00

109 lines
3.6 KiB
JavaScript

import { execFileSync } from 'node:child_process';
export function shellQuote(value) {
return `'${String(value).replace(/'/g, `'\\''`)}'`;
}
export function runRemote(cloudHost, command) {
return execFileSync('ssh', [cloudHost, command], {
encoding: 'utf8',
stdio: 'pipe',
maxBuffer: 32 * 1024 * 1024,
});
}
export function resolveHostedTenantRootDataDir(tenantId) {
return `/data/tenants/${tenantId}`;
}
export function resolveHostedTenantOrgDataDir(tenantId, orgId) {
const scopedOrgId = String(orgId || tenantId).trim();
if (scopedOrgId === '' || scopedOrgId === 'default') {
return resolveHostedTenantRootDataDir(tenantId);
}
return `/data/tenants/${tenantId}/orgs/${scopedOrgId}`;
}
export function hostedTenantContainerName(tenantId) {
const normalizedTenantId = String(tenantId || '').trim();
if (!normalizedTenantId) {
throw new Error('tenantId is required');
}
return `pulse-${normalizedTenantId}`;
}
function remoteFailureOutput(error) {
const remoteOutput = ['stderr', 'stdout']
.map((field) => error?.[field])
.filter((value) => value !== undefined && value !== null && String(value).trim() !== '')
.map((value) => String(value).trim())
.join('\n');
if (remoteOutput) {
return remoteOutput;
}
return String(error?.message || '').trim();
}
export function hostedTenantRuntimeExistsScript(tenantId) {
const containerName = hostedTenantContainerName(tenantId);
return `
set -eu
container=${shellQuote(containerName)}
if ! docker inspect "$container" >/dev/null 2>&1; then
echo "hosted tenant runtime container $container does not exist" >&2
echo "create or restore an active hosted tenant before running hosted mobile proof seeding" >&2
exit 72
fi
docker inspect --format '{{.Name}} {{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container"
`;
}
export function assertHostedTenantRuntimeExists(cloudHost, tenantId, runner = runRemote) {
const containerName = hostedTenantContainerName(tenantId);
try {
return runner(cloudHost, `sh -lc ${shellQuote(hostedTenantRuntimeExistsScript(tenantId))}`);
} catch (error) {
const detail = remoteFailureOutput(error);
throw new Error([
`hosted tenant runtime ${containerName} is unavailable on ${cloudHost}`,
'Hosted mobile proof seeding requires an active hosted tenant container before it mutates approval or token data.',
detail,
].filter(Boolean).join('\n'));
}
}
export function restartHostedTenantRuntime(cloudHost, tenantId, runner = runRemote) {
const containerName = hostedTenantContainerName(tenantId);
const script = `
set -eu
container=${shellQuote(containerName)}
if ! docker inspect "$container" >/dev/null 2>&1; then
echo "hosted tenant runtime container $container does not exist" >&2
echo "create or restore an active hosted tenant before restarting hosted mobile proof state" >&2
exit 72
fi
docker restart "$container" >/dev/null
deadline=$(( $(date +%s) + 60 ))
while [ "$(date +%s)" -lt "$deadline" ]; do
state="$(docker inspect --format '{{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container" 2>/dev/null || true)"
case "$state" in
"running healthy"|"running none")
exit 0
;;
esac
sleep 1
done
echo "timed out waiting for $container to become ready" >&2
docker inspect --format '{{json .State}}' "$container" >&2 || true
exit 1
`;
try {
runner(cloudHost, `sh -lc ${shellQuote(script)}`);
} catch (error) {
const detail = remoteFailureOutput(error);
throw new Error([
`failed to restart hosted tenant runtime ${containerName} on ${cloudHost}`,
detail,
].filter(Boolean).join('\n'));
}
}