{"record":{"id":"aec4a49f2a2d7fb1","repo":"kubernetes/kops","slug":"error-updating-keyset-q-v","errorCode":null,"errorMessage":"error updating keyset %q: %v","messagePattern":"error updating keyset %q: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"upup/pkg/fi/clientset_castore.go","lineNumber":251,"sourceCode":"\t\terr = nil\n\t}\n\tif err == nil {\n\t\tif oldKeyset == nil {\n\t\t\tcreate = true\n\t\t} else {\n\t\t\tkopsKeyset.ObjectMeta = oldKeyset.ObjectMeta\n\t\t}\n\t} else {\n\t\treturn fmt.Errorf(\"error reading keyset %q: %v\", name, err)\n\t}\n\n\tif create {\n\t\tif _, err := client.Create(ctx, kopsKeyset, metav1.CreateOptions{}); err != nil {\n\t\t\treturn fmt.Errorf(\"error creating keyset %q: %v\", name, err)\n\t\t}\n\t} else {\n\t\tif _, err := client.Update(ctx, kopsKeyset, metav1.UpdateOptions{}); err != nil {\n\t\t\treturn fmt.Errorf(\"error updating keyset %q: %v\", name, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// addSSHCredential saves the specified SSH Credential to the registry, doing an update or insert\nfunc (c *ClientsetCAStore) addSSHCredential(ctx context.Context, publicKey string) error {\n\tcreate := false\n\tclient := c.clientset.SSHCredentials(c.namespace)\n\tsshCredential, err := client.Get(ctx, \"admin\", metav1.GetOptions{})\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tsshCredential = nil\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"error reading SSHCredential: %v\", err)\n\t\t}\n\t}\n\tif sshCredential == nil {","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/upup/pkg/fi/clientset_castore.go#L233-L269","documentation":"ClientsetCAStore.storeKeyset persists a kops Keyset (CA keys, secret keys, etc.) to the Kubernetes cluster via the kops clientset. When the keyset already exists (create==false) it calls Update; any API error from Update is wrapped as 'error updating keyset %q: %v'. This indicates the kops API server rejected the update of the keyset object.","triggerScenarios":"Calling StoreKeyset on a ClientsetCAStore for a keyset that exists but fails Update: API server connectivity failures, RBAC denial on kops Keyset resources, resourceVersion conflict (concurrent writers), invalid object, or namespace missing.","commonSituations":"kOps CLI run against a cluster where the kops system namespace/state store was partially deleted; concurrent `kops update cluster` runs mutating the same keyset; user kubeconfig lacking permissions to the kops namespace; network partition to the API server during `kops create secret`.","solutions":["Inspect the wrapped %v cause: if it is a conflict, re-run the command so it re-reads and retries the update","Verify RBAC allows create/update on keysets.kops.k8s.io in the kops namespace","Check API server reachability and kubeconfig context (`kubectl get nodes`)","If the keyset is corrupt/missing, delete it and re-run the operation so create path is taken"],"exampleFix":"// before (caller ignores conflicts, blind retry fails)\nif err := keysetStore.StoreKeyset(ctx, name, keyset); err != nil { return err }\n// after (retry once on conflict)\nif err := keysetStore.StoreKeyset(ctx, name, keyset); err != nil {\n\tif strings.Contains(err.Error(), \"conflict\") {\n\t\ttime.Sleep(2 * time.Second)\n\t\terr = keysetStore.StoreKeyset(ctx, name, keyset)\n\t}\n\tif err != nil { return err }\n}","handlingStrategy":"retry","validationCode":"// pre-check API access\nif err := clientset.Keysets(ns).List(ctx, metav1.ListOptions{}); err != nil {\n\treturn fmt.Errorf(\"no API access for keysets: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if err := store.StoreKeyset(ctx, name, ks); err != nil {\n\tif isConflict(err) { time.Sleep(time.Second); err = store.StoreKeyset(ctx, name, ks) }\n\tif err != nil { return fmt.Errorf(\"keyset %s persist failed: %w\", name, err) }\n}","preventionTips":["Avoid concurrent kops writers to the same cluster state","Grant RBAC for keysets.kops.k8s.io create/update before operations","Check the wrapped cause (%v) before deciding on retry vs abort","Keep the kubeconfig context pointed at the right cluster"],"tags":["kubernetes","keyset","api-server","rbac"],"backgroundTag":"kubernetes-api-write-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"}