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) {
			continue

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Validate the DO API token (doctl account get) and re-authenticate if expired
  2. Retry the kOps delete cluster command — list operations are safe to repeat
  3. Check https://status.digitalocean.com for API incidents if failures persist
  4. 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

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


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/69df1f5c2f8e576a. Report an issue: GitHub.