statusupdate
About 459 words
Ensures reconcilers update resource status after making changes.
Category
Kubernetes
What It Checks
This analyzer detects reconcilers that modify resources but don't update status, leaving users without visibility into the actual state.
Only a write through the status subresource counts as a status update: Status().Update(), Status().Patch(), Status().Apply() or the same calls on SubResource("status"), also through a variable assigned one (sw := r.Status()). Update and Patch on the object itself ignore the status subresource, so assigning obj.Status fields and then calling r.Update() leaves the status unsaved and is reported. A Patch on a patch helper, such as cluster-api's patch.Helper, persists spec and status together and counts as a status update; the analyzer tells a helper from a client by the Status method a client has. A call of a function or method declared in the same package, such as an updateStatus helper, counts when it writes the status itself or through another such helper.
Why It Matters
Status communicates:
- Current state to users and other controllers
- Readiness for dependent operations
- Error conditions and reasons
Without status updates, users can't tell what's happening.
Examples
Bad: No Status Update
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
obj := &myv1.MyResource{}
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Create deployment
if err := r.createDeployment(ctx, obj); err != nil {
return ctrl.Result{}, err
}
// Status never updated! Users don't know deployment was created.
return ctrl.Result{}, nil
}Bad: Status Set but Saved With Update
obj.Status.Phase = "Ready"
// Update writes the object, not its status subresource: Phase is lost.
return ctrl.Result{}, r.Update(ctx, obj)Good: Status Updated
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
obj := &myv1.MyResource{}
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Create deployment
if err := r.createDeployment(ctx, obj); err != nil {
obj.Status.Phase = "Failed"
obj.Status.Message = err.Error()
r.Status().Update(ctx, obj)
return ctrl.Result{}, err
}
// Update status
obj.Status.Phase = "Ready"
obj.Status.DeploymentName = deploymentName(obj)
if err := r.Status().Update(ctx, obj); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}Good: Using Conditions
import "k8s.io/apimachinery/pkg/api/meta"
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// ... get object ...
// Set condition
condition := metav1.Condition{
Type: "Ready",
Status: metav1.ConditionTrue,
ObservedGeneration: obj.Generation,
LastTransitionTime: metav1.Now(),
Reason: "ReconcileSucceeded",
Message: "All resources are ready",
}
meta.SetStatusCondition(&obj.Status.Conditions, condition)
// Update status
if err := r.Status().Update(ctx, obj); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}Configuration
# .golint-sl.yaml
analyzers:
statusupdate: true # enabled by defaultWhen to Disable
- Non-Kubernetes projects
- Controllers that don't manage custom resources
analyzers:
statusupdate: falseRelated Analyzers
- reconciler - Reconciler patterns
- sideeffects - Side effect detection
