{"record":{"id":"c9c6f8cce6d63d18","repo":"kubernetes/kops","slug":"failed-to-delete-vpc-s-id-s-s","errorCode":null,"errorMessage":"failed to delete VPC %s (ID %s): %s","messagePattern":"failed to delete VPC (.+?) \\(ID (.+?)\\): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/resources/digitalocean/resources.go","lineNumber":273,"sourceCode":"\tc := cloud.(do.DOCloud)\n\tdropletID, err := strconv.Atoi(t.ID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to convert droplet ID to int: %s\", err)\n\t}\n\n\t_, err = c.DropletsService().Delete(context.TODO(), dropletID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to delete droplet: %d, err: %s\", dropletID, err)\n\t}\n\n\treturn nil\n}\n\nfunc deleteVPC(cloud fi.Cloud, t *resources.Resource) error {\n\tc := cloud.(do.DOCloud)\n\t_, err := c.VPCsService().Delete(context.TODO(), t.ID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to delete VPC %s (ID %s): %s\", t.Name, t.ID, err)\n\t}\n\n\treturn nil\n}\n\nfunc deleteVolume(cloud fi.Cloud, t *resources.Resource) error {\n\tc := cloud.(do.DOCloud)\n\tvolume := t.Obj.(godo.Volume)\n\tfor _, dropletID := range volume.DropletIDs {\n\t\taction, resp, err := c.VolumeActionService().DetachByDropletID(context.TODO(), volume.ID, dropletID)\n\t\tif err != nil {\n\t\t\tif resp != nil && resp.StatusCode == http.StatusNotFound {\n\t\t\t\t// Volume is already detached, nothing to do.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"failed to detach volume %s: %s\", volume.ID, err)\n\t\t}\n","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/resources/digitalocean/resources.go#L255-L291","documentation":"deleteVPC wraps the godo VPCsService.Delete failure during DigitalOcean cluster teardown. It fires when the DO API refuses to delete the cluster's VPC (non-empty, permission denied, or transient API error); the VPC name and ID are echoed for correlation.","triggerScenarios":"VPC delete API returns 404 (already deleted), 409/422 (VPC still in use — resources like droplets/LBs remain attached), 403 (scope), or transient API error.","commonSituations":"Deleting a VPC that DO does not allow removing because member resources still exist; VPC deleted manually earlier; default VPC cannot be deleted; token scope too narrow.","solutions":["Check the DigitalOcean API token and VPC delete permissions","Ensure no droplets/resources remain in the VPC, then retry","Verify the VPC ID still exists; 404 means already deleted"],"exampleFix":"// before\n# VPC delete rejected: member droplets still attached\n// after\nkops delete cluster --cloud digitalocean ...  # removes droplets before VPC; or doctl compute droplet delete <id> first","handlingStrategy":"retry","validationCode":"vpcs, _, _ := c.VPCsService().List(context.TODO(), &godo.ListOptions{})\nfor _, v := range vpcs {\n\tif v.ID == vpcID && v.Default {\n\t\treturn errors.New(\"cannot delete the default VPC\")\n\t}\n}","typeGuard":null,"tryCatchPattern":"if _, err := c.VPCsService().Delete(context.TODO(), t.ID); err != nil {\n\tvar gerr *godo.ErrorResponse\n\tif errors.As(err, &gerr) && gerr.Response.StatusCode == http.StatusNotFound {\n\t\treturn nil // already deleted\n\t}\n\treturn fmt.Errorf(\"failed to delete VPC %s (ID %s): %w\", t.Name, t.ID, err)\n}","preventionTips":["Delete all member resources (droplets, LBs) before deleting the VPC","Never target the region default VPC","Treat 404 as idempotent success","Check token vpc write scope"],"tags":["digitalocean","vpc","api-error"],"backgroundTag":"cloud-resource-delete-failed","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}