{"record":{"id":"f95cb1614d440917","repo":"kubernetes/kops","slug":"failed-to-retrieve-droplet-d-w","errorCode":null,"errorMessage":"failed to retrieve droplet %d: %w","messagePattern":"failed to retrieve droplet (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/nodeidentity/do/identify.go","lineNumber":150,"sourceCode":"\t\treturn nil, errors.New(\"provider ID number cannot be empty\")\n\t}\n\n\tif i.cacheEnabled {\n\t\tif obj, exists, err := i.cache.GetByKey(instanceID); err != nil {\n\t\t\tklog.Warningf(\"Nodeidentity info cache lookup failure: %v\", err)\n\t\t} else if exists {\n\t\t\treturn obj.(*nodeidentity.Info), nil\n\t\t}\n\t}\n\n\tdropletID, err := strconv.Atoi(instanceID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert provider ID number %q: %s\", instanceID, err)\n\t}\n\n\tdroplet, _, err := i.doClient.Droplets.Get(ctx, dropletID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to retrieve droplet %d: %w\", dropletID, err)\n\t}\n\tif droplet == nil {\n\t\treturn nil, fmt.Errorf(\"droplet %d not found\", dropletID)\n\t}\n\tif droplet.Status != \"active\" && droplet.Status != \"new\" {\n\t\treturn nil, fmt.Errorf(\"droplet %d has unexpected status %q\", dropletID, droplet.Status)\n\t}\n\n\tinfo := &nodeidentity.Info{\n\t\tInstanceID: instanceID,\n\t\tLabels:     labelsFromTags(droplet.Tags),\n\t}\n\n\tif i.cacheEnabled {\n\t\tif err := i.cache.Add(info); err != nil {\n\t\t\tklog.Warningf(\"Failed to add node identity info to cache: %v\", err)\n\t\t}\n\t}","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/nodeidentity/do/identify.go#L132-L168","documentation":"IdentifyNode calls doClient.Droplets.Get to fetch the droplet by numeric ID. If the DigitalOcean API returns an error (auth failure, rate limit, network error, or API-side not-found), the error is wrapped with the droplet ID using %w.","triggerScenarios":"DigitalOcean API call fails: invalid/expired API token, rate limiting (429), network outage, or the droplet was deleted (404 surfaced by the API client as an error).","commonSituations":"Expired or revoked DO API token on the controller; DO API rate limits hit at scale; droplet deleted between providerID assignment and lookup; regional API outage.","solutions":["Inspect the wrapped cause (%w) for the HTTP status — 401/403 means fix the API token, 429 means back off, 404 means the droplet is gone","Verify the digitalocean token credential is valid and has read scope for droplets","Check DigitalOcean API status and retry with backoff if rate-limited or during an outage","If the droplet was deleted, remove the stale Node object from the cluster"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// verify credentials before calling\ntoken := os.Getenv(\"DIGITALOCEAN_ACCESS_TOKEN\")\nif token == \"\" { return errors.New(\"missing DO token\") }","typeGuard":null,"tryCatchPattern":"info, err := IdentifyNode(ctx, providerID)\nif err != nil {\n    var apiErr *goauthorize.Error\n    if errors.As(err, &apiErr) {\n        switch {\n        case apiErr.StatusCode == 429: // backoff and retry\n        case apiErr.StatusCode == 401: // refresh/fix token\n        default: // alert on API outage\n        }\n    }\n}","preventionTips":["Rotate and validate the DO API token regularly with droplet read scope","Respect rate limits with backoff on 429","Handle deleted droplets by cleaning up stale Node objects","Monitor DigitalOcean API status in the controller's alerting"],"tags":["digitalocean","api","droplet","auth","network"],"backgroundTag":"cloud-api-call-failed","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}