hub v0.61.0 + felhom-tenantsync v1.1.0: Customer RESET (middle lifecycle tier)
One operator action returns a customer to pre-first-install: all operational state dies (offsite repo, PBS namespace+backups, DR recipe, one-time secret, claim state, retained escrow custody); identity + basic config + provenance + events survive. Sits between host delete and customer Delete. - store/customer_reset.go: customer_resets journal, live inventory, ack-gated purge (never touches identity/provenance/events), DeleteClaim. - claim.ResetToUnclaimed: delete claim row -> fresh code next onboarding. - offsite.Deprovision (idempotent) + OffsiteIdentifier + ClearProvisionedDescriptor. - tenantsync.Deprovision + felhom-tenantsync.sh deprovision op (destroys ns + backup groups + token; shared user untouched; idempotent). - web/customer_reset.go: GET reset -> inventory JSON; POST -> orchestration (external teardown FIRST, DB purge LAST; refuse-while-hosts; typed-id + separate escrow ack). Amber RESET card distinct from red Danger-zone Delete. - Red-proofs: ack-gate + partial-failure resumability (both proven red); store ack-gating + journal round-trip; offsite idempotency + descriptor clear; RESET-card render. Green: build + vet + test.
This commit is contained in:
@@ -389,3 +389,54 @@ func TestProvision_DisableNoDeprovision(t *testing.T) {
|
||||
t.Fatal("disable must NOT deprovision (data-loss guard)")
|
||||
}
|
||||
}
|
||||
|
||||
// Customer RESET (v0.61.0) — Deprovision DESTROYS the labelled shared sub-account, and is idempotent
|
||||
// (a second call finds nothing and succeeds). This is the deliberate teardown the disable-guard above
|
||||
// deliberately does NOT do.
|
||||
func TestDeprovision_SharedIdempotent(t *testing.T) {
|
||||
p, fake, _ := newTestProvisioner(t)
|
||||
if _, err := p.ProvisionOffsite(context.Background(), "cust-d", Input{Enabled: true, Type: "shared", QuotaGB: 10}); err != nil {
|
||||
t.Fatalf("provision: %v", err)
|
||||
}
|
||||
if fake.CreatedSubaccounts != 1 {
|
||||
t.Fatalf("precondition: want 1 subaccount, got %d", fake.CreatedSubaccounts)
|
||||
}
|
||||
if err := p.Deprovision(context.Background(), "cust-d", "shared"); err != nil {
|
||||
t.Fatalf("deprovision: %v", err)
|
||||
}
|
||||
if fake.DeletedSubaccounts != 1 {
|
||||
t.Fatalf("want 1 subaccount deleted, got %d", fake.DeletedSubaccounts)
|
||||
}
|
||||
// Idempotent: nothing labelled now → success, no extra delete.
|
||||
if err := p.Deprovision(context.Background(), "cust-d", "shared"); err != nil {
|
||||
t.Fatalf("second deprovision (idempotent) errored: %v", err)
|
||||
}
|
||||
if fake.DeletedSubaccounts != 1 {
|
||||
t.Fatalf("idempotent re-run deleted again: %d", fake.DeletedSubaccounts)
|
||||
}
|
||||
}
|
||||
|
||||
// ClearProvisionedDescriptor keeps the tier CHOICE (enabled/type/quota/box_type) and drops every
|
||||
// PROVISIONED field — the pre-first-install shape a RESET returns the customer to.
|
||||
func TestClearProvisionedDescriptor(t *testing.T) {
|
||||
in := `{"git":{"token":"x"},"offsite":{"enabled":true,"type":"shared","host":"u1-sub3.your-storagebox.de","user":"u1-sub3","port":23,"repo_path":"/home/felhom","quota_gb":100,"box_type":"","host_fingerprint":"SHA256:abc"}}`
|
||||
out, err := ClearProvisionedDescriptor(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(out, `"enabled":true`) || !strings.Contains(out, `"type":"shared"`) || !strings.Contains(out, `"quota_gb":100`) {
|
||||
t.Errorf("tier choice lost: %s", out)
|
||||
}
|
||||
for _, gone := range []string{"your-storagebox.de", "u1-sub3", "repo_path", "host_fingerprint", `"port"`} {
|
||||
if strings.Contains(out, gone) {
|
||||
t.Errorf("provisioned field %q survived: %s", gone, out)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(out, `"git"`) {
|
||||
t.Errorf("unrelated config keys dropped: %s", out)
|
||||
}
|
||||
// No-op-safe: absent offsite block returns input unchanged.
|
||||
if got, _ := ClearProvisionedDescriptor(`{"git":{"token":"x"}}`); got != `{"git":{"token":"x"}}` {
|
||||
t.Errorf("no-offsite clear mutated config: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user