{"record":{"id":"69df1f5c2f8e576a","repo":"kubernetes/kops","slug":"failed-to-list-ssh-keys-v","errorCode":null,"errorMessage":"failed to list ssh keys: %v","messagePattern":"failed to list ssh keys: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/resources/digitalocean/resources.go","lineNumber":400,"sourceCode":"\t}\n\n\top.Dump.Instances = append(op.Dump.Instances, i)\n\n\treturn nil\n}\n\n// listSSHKeys finds the SSH keys kops uploaded for this cluster. DigitalOcean\n// keys are account-scoped, so they are matched by the name kops gives them in\n// pkg/model/names.go: \"kubernetes.<cluster name>-<fingerprint>\". A cluster that\n// sets spec.sshKeyName reuses a pre-existing key it does not own, and Find in\n// dotasks/sshkey.go leaves that key alone, so this deliberately does not match it.\nfunc listSSHKeys(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) {\n\tc := cloud.(do.DOCloud)\n\tvar resourceTrackers []*resources.Resource\n\n\tkeys, err := c.GetAllSSHKeys()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to list ssh keys: %v\", err)\n\t}\n\n\tresourceTrackers = append(resourceTrackers, filterClusterSSHKeys(keys, clusterName)...)\n\n\treturn resourceTrackers, nil\n}\n\n// filterClusterSSHKeys selects the keys kops named for this cluster. The trailing\n// \"-\" before the fingerprint matters: without it \"foo.k8s.local\" would also match\n// the keys of \"foo.k8s.local.example.com\", and these keys are account-scoped and\n// shared with every other cluster in the account.\nfunc filterClusterSSHKeys(keys []godo.Key, clusterName string) []*resources.Resource {\n\tkeyPrefix := \"kubernetes.\" + clusterName + \"-\"\n\n\tvar resourceTrackers []*resources.Resource\n\tfor _, key := range keys {\n\t\tif !strings.HasPrefix(key.Name, keyPrefix) {\n\t\t\tcontinue","sourceCodeStart":382,"sourceCodeEnd":418,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/resources/digitalocean/resources.go#L382-L418","documentation":"listSSHKeys wraps an error from DOCloud.GetAllSSHKeys, which pages through the DigitalOcean account SSH keys list API. It is thrown when kOps cannot enumerate SSH keys while building the list of cluster-related resources during delete cluster discovery. The original API error is preserved via %v.","triggerScenarios":"Any error from the DO /v2/account/keys list endpoint: invalid or expired token (401), insufficient scope (403), rate limiting (429), pagination failures, or network errors.","commonSituations":"Bad or revoked DIGITALOCEAN_ACCESS_TOKEN in the environment; DO API outage or degradation; hitting rate limits in CI with many concurrent kOps operations.","solutions":["Validate the DO API token (doctl account get) and re-authenticate if expired","Retry the kOps delete cluster command — list operations are safe to repeat","Check https://status.digitalocean.com for API incidents if failures persist","Back off and retry on 429 rate-limit responses"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Sanity-check credentials before listing\n_, _, err := c.client.Account.Get(context.TODO())\nif err != nil {\n\treturn fmt.Errorf(\"digitalocean API token invalid or unreachable: %v\", err)\n}","typeGuard":null,"tryCatchPattern":"keys, err := c.GetAllSSHKeys()\nif err != nil {\n\tif gerr, ok := err.(*godo.ErrorResponse); ok && gerr.Response.StatusCode == http.StatusTooManyRequests {\n\t\t// backoff and retry listing\n\t}\n\treturn nil, fmt.Errorf(\"failed to list ssh keys: %v\", err)\n}","preventionTips":["Run doctl account get to validate the token before cluster operations","Avoid many concurrent kOps invocations against the same DO account to dodge rate limits","Retry list operations — they are read-only and safe","Subscribe to DO status updates to anticipate API outages"],"tags":["digitalocean","ssh","api-error","list-operation"],"backgroundTag":"api-list-request-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"}