Skip to content

Commit 7de4f3a

Browse files
committed
Delete KV namespace when removing a worker
The remove command now deletes the associated KV namespace (tokfresh-tokens-{name}) after deleting the worker script. KV cleanup is best-effort — if the namespace is not found or deletion fails, the worker removal still succeeds with a warning logged.
1 parent ab58242 commit 7de4f3a

3 files changed

Lines changed: 80 additions & 1 deletion

File tree

cmd/remove.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,19 @@ func runRemove(cmd *cobra.Command, args []string) error {
5757
cfDeleteOK := false
5858
if authErr == nil {
5959
deleteErr := ui.RunWithSpinnerFullscreen("Deleting worker from Cloudflare...", func() error {
60-
return cloudflare.DeleteWorker(auth.AccountID, auth.Token, name)
60+
if err := cloudflare.DeleteWorker(auth.AccountID, auth.Token, name); err != nil {
61+
return err
62+
}
63+
kvTitle := fmt.Sprintf("tokfresh-tokens-%s", name)
64+
nsID, findErr := cloudflare.FindKV(auth.AccountID, auth.Token, kvTitle)
65+
if findErr != nil {
66+
log.Debug("KV namespace not found, skipping deletion", "title", kvTitle)
67+
return nil
68+
}
69+
if delErr := cloudflare.DeleteKV(auth.AccountID, auth.Token, nsID); delErr != nil {
70+
log.Warn("failed to delete KV namespace", "title", kvTitle, "error", delErr)
71+
}
72+
return nil
6173
})
6274
if deleteErr != nil {
6375
log.Warn("failed to delete worker from Cloudflare", "error", deleteErr)

internal/cloudflare/api.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,33 @@ func DeleteWorker(accountID, token, workerName string) error {
412412
}
413413
}
414414

415+
// DeleteKV deletes a KV namespace.
416+
func DeleteKV(accountID, token, namespaceID string) error {
417+
url := fmt.Sprintf("%s/accounts/%s/storage/kv/namespaces/%s", cfAPIBase, accountID, namespaceID)
418+
419+
log.Debug("deleting KV namespace", "url", url)
420+
start := time.Now()
421+
resp, err := doRequest("DELETE", url, token, nil)
422+
if err != nil {
423+
return fmt.Errorf("KV delete request failed: %w", err)
424+
}
425+
defer func() { _ = resp.Body.Close() }()
426+
log.Debug("KV delete response", "status", resp.StatusCode, "elapsed", time.Since(start))
427+
428+
switch resp.StatusCode {
429+
case http.StatusOK:
430+
return nil
431+
case http.StatusNotFound:
432+
return nil
433+
case http.StatusForbidden, http.StatusUnauthorized:
434+
body, _ := io.ReadAll(resp.Body)
435+
return fmt.Errorf("%w: %s", ErrUnauthorized, string(body))
436+
default:
437+
body, _ := io.ReadAll(resp.Body)
438+
return fmt.Errorf("KV delete failed (%d): %s", resp.StatusCode, string(body))
439+
}
440+
}
441+
415442
// parseCFErrorCode extracts the first error code from a CF API error response.
416443
// CF error responses: {"success":false,"errors":[{"code":10063,"message":"..."}]}
417444
func parseCFErrorCode(body []byte) int {

internal/cloudflare/api_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,3 +585,43 @@ func TestDeleteWorker_Forbidden_ReturnsUnauthorized(t *testing.T) {
585585
t.Errorf("expected ErrUnauthorized, got: %v", err)
586586
}
587587
}
588+
589+
func TestDeleteKV(t *testing.T) {
590+
t.Run("success", func(t *testing.T) {
591+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
592+
if r.Method != "DELETE" {
593+
t.Errorf("expected DELETE, got %s", r.Method)
594+
}
595+
if !strings.Contains(r.URL.Path, "/storage/kv/namespaces/ns-123") {
596+
t.Errorf("wrong path: %s", r.URL.Path)
597+
}
598+
w.WriteHeader(http.StatusOK)
599+
}))
600+
defer server.Close()
601+
602+
origBase := cfAPIBase
603+
cfAPIBase = server.URL
604+
defer func() { cfAPIBase = origBase }()
605+
606+
err := DeleteKV("acc", "tok", "ns-123")
607+
if err != nil {
608+
t.Fatal(err)
609+
}
610+
})
611+
612+
t.Run("not found is not an error", func(t *testing.T) {
613+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
614+
w.WriteHeader(http.StatusNotFound)
615+
}))
616+
defer server.Close()
617+
618+
origBase := cfAPIBase
619+
cfAPIBase = server.URL
620+
defer func() { cfAPIBase = origBase }()
621+
622+
err := DeleteKV("acc", "tok", "ns-missing")
623+
if err != nil {
624+
t.Fatalf("expected nil for 404, got: %v", err)
625+
}
626+
})
627+
}

0 commit comments

Comments
 (0)