Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion internal/cli/delete.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -137,6 +137,14 @@ func runDelete(ctx context.Context, p *ui.Printer, pr prompter, o deleteOpts) er
// unreachable precisely because it's being retired); warn and continue.
if !o.force {
if st, found, lerr := lookupClientStatus(ctx, client, prof.ActiveClientID); lerr != nil {
// A 426 (CLI too old) won't recover by continuing — the whole offboard
// talks to the same backend, so fail fast with the upgrade message rather
// than imply the guard was merely skipped. Other errors (5xx/429/network)
// are transient: warn and continue, since the teardown is the real gate.
var ue *api.UpgradeRequiredError
if errors.As(lerr, &ue) {
return &exitError{code: 1, err: lerr}
}
p.Hintf("Couldn't check whether this client is still online (%v) — continuing; pass --force to skip this check.", lerr)
} else if !found {
// The stored id isn't among this account's clients — likely a stale
Expand DownExpand Up@@ -233,7 +241,12 @@ func runDelete(ctx context.Context, p *ui.Printer, pr prompter, o deleteOpts) er
prof.ActiveClientID, prof.ActiveClientName, prof.ActiveClientNamespace = "", "", ""
if o.keepData {
if serr := cfg.Save(); serr != nil {
p.Warnf("Kept local data, but couldn't clear the active-client pointer (%v).", serr)
// The in-memory pointer was cleared but not persisted — the on-disk config
// still names the revoked client. Mark degraded so the closing doesn't read
// as a clean offboard, and tell the user it needs a hand.
degraded = true
p.Warnf("Kept local data, but couldn't clear the stored active-client pointer (%v) — "+
"the on-disk config still names the revoked client; run `tracebloc logout` or remove it by hand.", serr)
} else {
p.Infof("Kept local data and config (~/.tracebloc); cleared the active-client pointer — --keep-data.")
}
Expand Down
30 changes: 30 additions & 0 deletions internal/cli/delete_test.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -299,6 +299,36 @@ func TestDelete_TeardownFailure_HonestClosing(t *testing.T) {
}
}

// A 426 (CLI too old) during the pre-offboard online check must fail fast with the
// upgrade message — not warn-and-continue into the revoke/teardown.
func TestDelete_Guard426_FailsFast(t *testing.T) {
revoked := false
withClientBackend(t, func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost && strings.Contains(r.URL.Path, "/revoke") {
revoked = true
}
if r.Method == http.MethodGet && r.URL.Path == "/edge-device/" {
w.WriteHeader(http.StatusUpgradeRequired) // 426
_, _ = w.Write([]byte(`{"error":"upgrade_required","min_version":"1.2.3"}`))
}
})
setActiveForDelete(t, "5", "gpu-box-01", "gpu-box-01")
fn := &fakeNodeboot{executable: filepath.Join(t.TempDir(), "tracebloc")}
fn.install(t)

var out bytes.Buffer
err := runDelete(context.Background(), ui.New(&out), nil, deleteOpts{yes: true})
if err == nil || !strings.Contains(err.Error(), "too old") {
t.Fatalf("want a fail-fast upgrade error, got: %v", err)
}
if revoked {
t.Error("revoke must NOT run after a 426 guard failure")
}
if len(fn.calls) != 0 {
t.Errorf("no teardown after a 426 guard failure, got: %v", fn.calls)
}
}

// --kubeconfig/--context must reach the helm uninstall — otherwise the release is
// uninstalled against the ambient current-context, which may be the wrong cluster.
func TestDelete_KubeconfigContext_ReachHelm(t *testing.T) {
Expand Down
Loading