kubernetes/kops · error
failed to list ssh keys: %v
Error message
failed to list ssh keys: %v
What it means
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.
Source
Thrown at pkg/resources/digitalocean/resources.go:400
}
op.Dump.Instances = append(op.Dump.Instances, i)
return nil
}
// listSSHKeys finds the SSH keys kops uploaded for this cluster. DigitalOcean
// keys are account-scoped, so they are matched by the name kops gives them in
// pkg/model/names.go: "kubernetes.<cluster name>-<fingerprint>". A cluster that
// sets spec.sshKeyName reuses a pre-existing key it does not own, and Find in
// dotasks/sshkey.go leaves that key alone, so this deliberately does not match it.
func listSSHKeys(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) {
c := cloud.(do.DOCloud)
var resourceTrackers []*resources.Resource
keys, err := c.GetAllSSHKeys()
if err != nil {
return nil, fmt.Errorf("failed to list ssh keys: %v", err)
}
resourceTrackers = append(resourceTrackers, filterClusterSSHKeys(keys, clusterName)...)
return resourceTrackers, nil
}
// filterClusterSSHKeys selects the keys kops named for this cluster. The trailing
// "-" before the fingerprint matters: without it "foo.k8s.local" would also match
// the keys of "foo.k8s.local.example.com", and these keys are account-scoped and
// shared with every other cluster in the account.
func filterClusterSSHKeys(keys []godo.Key, clusterName string) []*resources.Resource {
keyPrefix := "kubernetes." + clusterName + "-"
var resourceTrackers []*resources.Resource
for _, key := range keys {
if !strings.HasPrefix(key.Name, keyPrefix) {
continueView on GitHub (pinned to 4c8573c808)
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
Defensive patterns
Strategy: retry
Validate before calling
// Sanity-check credentials before listing
_, _, err := c.client.Account.Get(context.TODO())
if err != nil {
return fmt.Errorf("digitalocean API token invalid or unreachable: %v", err)
} Try / catch
keys, err := c.GetAllSSHKeys()
if err != nil {
if gerr, ok := err.(*godo.ErrorResponse); ok && gerr.Response.StatusCode == http.StatusTooManyRequests {
// backoff and retry listing
}
return nil, fmt.Errorf("failed to list ssh keys: %v", err)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Bad or revoked DIGITALOCEAN_ACCESS_TOKEN in the environment; DO API outage or degradation; hitting rate limits in CI with many concurrent kOps operations.
Related errors
- failed to delete ssh key %s (ID %s): %s
- failed to apply resource record set: %s, err: %s
- failed to list lbs: %v
- failed to delete droplet: %d, err: %s
- failed to delete VPC %s (ID %s): %s
AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05).
Data as JSON: /api/errors/69df1f5c2f8e576a.
Report an issue: GitHub.