{"record":{"id":"d8bde4c283959162","repo":"kubernetes/kops","slug":"rollingupdate-is-missing-a-k8s-client","errorCode":null,"errorMessage":"rollingUpdate is missing a k8s client","messagePattern":"rollingUpdate is missing a k8s client","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/instancegroups/instancegroups.go","lineNumber":127,"sourceCode":"\tval = strings.TrimSpace(val)\n\tval = strings.ToLower(val)\n\tswitch val {\n\tcase \"n\":\n\t\tklog.Info(\"User signaled to stop\")\n\t\tos.Exit(3)\n\tcase \"a\":\n\t\tklog.Info(\"Always Yes, stop prompting for rest of hosts\")\n\t\tstopPrompting = true\n\t}\n\treturn stopPrompting, err\n}\n\n// RollingUpdate performs a rolling update on a list of instances.\nfunc (c *RollingUpdateCluster) rollingUpdateInstanceGroup(ctx context.Context, group *cloudinstances.CloudInstanceGroup, sleepAfterTerminate time.Duration) (err error) {\n\tisBastion := group.InstanceGroup.IsBastion()\n\t// Do not need a k8s client if you are doing cloudonly.\n\tif c.K8sClient == nil && !c.CloudOnly {\n\t\treturn fmt.Errorf(\"rollingUpdate is missing a k8s client\")\n\t}\n\n\tnoneReady := len(group.Ready) == 0\n\tnumInstances := len(group.Ready) + len(group.NeedUpdate)\n\tupdate := group.NeedUpdate\n\tif c.Force {\n\t\tupdate = append(update, group.Ready...)\n\t}\n\n\tif len(update) == 0 {\n\t\treturn nil\n\t}\n\n\tif isBastion {\n\t\tklog.V(3).Info(\"Not validating the cluster as instance is a bastion.\")\n\t} else if err = c.maybeValidate(\"\", 1, group); err != nil {\n\t\treturn err\n\t}","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/instancegroups/instancegroups.go#L109-L145","documentation":"rollingUpdateInstanceGroup needs a Kubernetes client to cordon/drain nodes and await readiness. If RollingUpdateCluster.K8sClient is nil and CloudOnly mode is not enabled, the update cannot safely proceed and returns this error before touching instances.","triggerScenarios":"Constructing RollingUpdateCluster without setting K8sClient and without setting CloudOnly=true, then calling RollingUpdate on a non-bastion group.","commonSituations":"Tooling that only manipulates cloud state forgetting the client, building the struct literal in tests/tools and omitting K8sClient, or intending cloud-only mode but not setting the flag.","solutions":["Set K8sClient on RollingUpdateCluster to an initialized kubernetes.Clientset for the cluster","Set CloudOnly=true if you intentionally want updates without Kubernetes API access","Verify the cluster API is reachable so a client can be constructed"],"exampleFix":"// before\nr := &RollingUpdateCluster{Cloud: cloud, Force: false}\n// after\nr := &RollingUpdateCluster{Cloud: cloud, K8sClient: clientset, Force: false}","handlingStrategy":"validation","validationCode":"// Guard before constructing the rolling update\nif r.K8sClient == nil && !r.CloudOnly {\n\treturn fmt.Errorf(\"refusing to roll: K8sClient is nil and CloudOnly is false\")\n}","typeGuard":"func canRoll(r *RollingUpdateCluster) bool {\n\treturn r.K8sClient != nil || r.CloudOnly\n}","tryCatchPattern":"err := c.RollingUpdate(ctx, groups, &k8sClients)\nif err != nil && strings.Contains(err.Error(), \"missing a k8s client\") {\n\treturn fmt.Errorf(\"initialize K8sClient or set CloudOnly=true: %w\", err)\n}","preventionTips":["Always set K8sClient when constructing RollingUpdateCluster unless intentionally cloud-only","Add an explicit constructor/validation function for RollingUpdateCluster","Document CloudOnly implications so it is not set as a workaround for a nil client"],"tags":["rolling-update","k8s-client","configuration"],"backgroundTag":"missing-k8s-client","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"}