Suspendedremoval
Some checks failed
Build and Push / build (push) Failing after 48s

This commit is contained in:
2026-05-01 18:07:00 +02:00
parent 7d58c78cb9
commit a5812dca9a
16 changed files with 880 additions and 90 deletions

View File

@@ -5,7 +5,7 @@ import {
updateTenantRequestStatus,
clearEncryptedSecrets,
} from "@/lib/db";
import { createTenant } from "@/lib/k8s";
import { createTenant, patchTenantSpec, setTenantAnnotation } from "@/lib/k8s";
import { sendApprovalEmail } from "@/lib/email";
import { decryptSecrets } from "@/lib/crypto";
import { writePackageSecrets } from "@/lib/openbao";
@@ -19,14 +19,26 @@ import { safeError } from "@/lib/errors";
/**
* POST /api/admin/requests/[id]/approve
* Approve a tenant request:
* 1. Decrypt stored package secrets (if any)
* 2. Write each package's secrets to OpenBao at secret/data/tenants/{tenant-name}/{package}
* 3. Null the encrypted_secrets column
* 4. Build workspace files (SOUL.md, AGENTS.md, TOOLS.md)
* 5. Create PiecedTenant CR
* 6. Update request status, notify customer.
* Also supports re-approving a previously rejected request (clears admin notes).
*
* Approve a request. Two paths depending on request_type:
*
* Provision (the original purpose):
* 1. Decrypt stored package secrets (if any)
* 2. Write each package's secrets to OpenBao
* 3. Null the encrypted_secrets column
* 4. Build workspace files (SOUL.md, AGENTS.md, TOOLS.md)
* 5. Create PiecedTenant CR
* 6. Update request status, notify customer.
* Supports re-approving a previously rejected request (clears admin notes).
*
* Resume (Bug 37a):
* 1. PATCH spec.suspend=false on the existing PiecedTenant CR.
* 2. Clear the `pieced.ch/resume-request-pending` annotation so the
* operator knows the request is settled (and doesn't pause its
* 60-day TTL forever — though now that the tenant isn't suspended,
* the timer is moot).
* 3. Mark request approved, notify customer.
* No CR creation, no secret materialisation, no workspace files.
*/
export async function POST(
request: Request,
@@ -60,6 +72,56 @@ export async function POST(
);
}
// Resume request: short path. Just patch the existing tenant, clear
// the annotation, mark approved.
if (tenantRequest.requestType === "resume") {
if (!tenantRequest.tenantName) {
// Shouldn't happen — resume requests are created with tenant_name
// set. Defensive 500 if it does.
return NextResponse.json(
{ error: "Resume request has no tenant_name" },
{ status: 500 }
);
}
try {
await patchTenantSpec(tenantRequest.tenantName, { suspend: false });
// Clear the annotation that pauses the operator's 60-day TTL.
// Best-effort — annotation cleanup is also done by the operator
// when it sees suspend=false on the next reconcile (it clears
// status.suspendedAt), but explicitly clearing here keeps the
// CR clean.
try {
await setTenantAnnotation(
tenantRequest.tenantName,
"pieced.ch/resume-request-pending",
null
);
} catch (e) {
console.warn(
"post-approve annotation clear failed; not blocking",
e
);
}
await updateTenantRequestStatus(id, "approved", adminNotes);
await sendApprovalEmail(tenantRequest, tenantRequest.tenantName).catch(
(e) => console.error("approval email failed:", e)
);
return NextResponse.json({
message: "Resume approved. Tenant is reactivating.",
tenantName: tenantRequest.tenantName,
});
} catch (e: any) {
console.error("Resume approval failed:", e);
return NextResponse.json(
{ error: safeError(e, "Failed to approve resume") },
{ status: 500 }
);
}
}
const isReApproval = tenantRequest.status === "rejected";
// Build the CR name: see `lib/tenant-naming.ts` for the format spec.