<p>Im having k8s controller which needs to install some resources and update the status and condition accordantly</p>
<p>The flow in the reconcile is like following:</p>
<ol>
<li>Install the resource and don’t wait</li>
<li>Call to the function <code>checkAvailability</code> and update the status accordantly if ready/ pending install/  error</li>
</ol>
<p>I’ve two main questions:</p>
<ol>
<li>This is the first time that I use status and conditions, is it right way or do I miss something</li>
<li>Sometimes when I do the update <code>r.Status().Update</code> I got error :<code>Operation cannot be fulfilled on eds.core.vtw.bmw.com “resouce01”: the object has been modified; please apply your changes to the latest version and try again , so I’ve added the check </code>conditionChanged` which solve the problem but not sure if its correct as I update the status once and if it doesn’t chanted I don’t touch it so user can see status ready from a while ago and the reconcile doesn’t  update the date and time for the ready condition as it skip it when its already “ready”</li>
</ol>
<p>I use the following</p>
<pre><code>func (r *ebdReconciler) checkHealth(ctx context.Context, req ctrl.Request, ebd ebdmanv1alpha1.ebd) (bool, error) {
    vfmReady, err := r.mbr.IsReady(ctx, req.Name, req.Namespace)
    condition := metav1.Condition{
        Type:               ebdmanv1alpha1.KubernetesvfmHealthy,
        Observebdneration: ebd.Generation,
        LastTransitionTime: metav1.Now(),
    }
    if err != nil {
        // There was an error checking readiness - Set status to false
        condition.Status = metav1.ConditionFalse
        condition.Reason = ebdmanv1alpha1.ReasonError
        condition.Message = fmt.Sprintf(&quot;Failed to check  vfm readiness: %v&quot;, err)
    } else if vfmReady {
        // The vfm is ready - Set status to true
        condition.Status = metav1.ConditionTrue
        condition.Reason = ebdmanv1alpha1.ReasonReady
        condition.Message = &quot;vfm custom resource is ready&quot;
    } else {
        // The vfm is not ready - Set status to false
        condition.Status = metav1.ConditionFalse
        condition.Reason = ebdmanv1alpha1.ResourceProgressing
        condition.Message = &quot;vfm custom resource is not ready&quot;
    }
    // Get the latest version of the ebd
    latestebd := ebdmanv1alpha1.ebd{}
    if err := r.Get(ctx, req.NamespacedName, &amp;latestebd); err != nil {
        return vfmReady, err
    }

    oldConditions := latestebd.Status.Conditions
    meta.SetStatusCondition(&amp;latestebd.Status.Conditions, condition)

    if !conditionChanged(&amp;oldConditions, &amp;latestebd.Status.Conditions, ebdmanv1alpha1.KubernetesvfmHealthy) {
        return vfmReady, nil
    }

    if err := r.Status().Update(ctx, &amp;latestebd); err != nil {
        r.Log.Error(err, &quot;failed to update vfm status&quot;)
        return vfmReady, err
    }
    return vfmReady, nil
}


func conditionChanged(oldConditions, newConditions *[]metav1.Condition, conditionType string) bool {
    newCondition := meta.FindStatusCondition(*newConditions, conditionType)
    oldCondition := meta.FindStatusCondition(*oldConditions, conditionType)
    if oldCondition == nil &amp;&amp; newCondition == nil {
        return false
    }
    if oldCondition == nil || newCondition == nil {
        return true
    }
    return oldCondition.Status != newCondition.Status || oldCondition.Reason != newCondition.Reason || oldCondition.Message != newCondition.Message
}
</code></pre>

PJEM
<p>To your questions:</p>
<ol>
<li><p>This is the first time that I use status and conditions, is it right way or do I miss something?</p>
<p>Your approach to managing the status and conditions of Kubernetes resources is generally fine. The <a href="https://book-v1.book.kubebuilder.io/basics/status_subresource.html" rel="nofollow noreferrer"><code>status</code> subresource</a> in a Kubernetes API object is typically used to represent the current state of the system, and it can include conditions.</p>
<p>A condition is a collection of fields that describe the state of an object in a more detailed way than just <code>true</code> or <code>false</code>. Each condition typically has a <code>type</code>, <code>status</code>, <code>reason</code>, <code>message</code>, and <code>lastTransitionTime</code>. Your code correctly sets these fields based on whether the <code>vfm</code> custom resource is ready or not.</p>
<p>It is good to note that conditions should be leveled - meaning they should be set to their current observed value regardless of their previous value. They should also be set (either <code>true</code>, <code>false</code>, or <code>unknown</code>) for all the significant or user-meaningful aspects of the component's current state. This makes conditions a good mechanism to indicate &quot;transient states&quot; like <code>Progressing</code> or <code>Degraded</code> that might be expected to change over time or based on external state.</p>
</li>
<li><p>Sometimes when I do the update <code>r.Status().Update</code> I got error: <code>Operation cannot be fulfilled on eds.core.vtw.bmw.com “resource01”: the object has been modified; please apply your changes to the latest version and try again</code>.</p>
<p>This error occurs because another client updated the same object while your controller was processing it. This could be another controller or even another instance of the same controller (if you run more than one).</p>
<p>One possible way to handle this is to use a retry mechanism that re-attempts the status update when this error occurs. In your case, you have implemented a <code>conditionChanged</code> check to only attempt the status update if the condition has changed. This is a good approach to avoid unnecessary updates, but it does not completely prevent the error, because another client could still update the object between your <code>Get</code> call and <code>Status().Update</code> call.</p>
<p>You could also consider using <code>Patch</code> instead of <code>Update</code> to modify the status, which reduces the risk of conflicting with other updates. Patching allows for partial updates to an object, so you are less likely to encounter conflicts.</p>
<p>Regarding the timing issue, you could consider updating the <code>LastTransitionTime</code> only when the status actually changes, instead of every time the health check is done. This would mean the <code>LastTransitionTime</code> reflects when the status last changed, rather than the last time the check was performed.</p>
<p>One thing to keep in mind is that frequent updates to the status subresource, even if the status does not change, can cause unnecessary API server load. You should strive to update the status only when it changes.</p>
</li>
</ol>
<p>A possible updated version of your <code>checkHealth</code> function considering those points could be:</p>
<pre class="lang-golang prettyprint-override"><code>func (r *ebdReconciler) checkHealth(ctx context.Context, req ctrl.Request, ebd ebdmanv1alpha1.ebd) (bool, error) {
    vfmReady, err := r.mbr.IsReady(ctx, req.Name, req.Namespace)
    condition := metav1.Condition{
        Type:   ebdmanv1alpha1.KubernetesvfmHealthy,
        Status: metav1.ConditionUnknown, // start with unknown status
    }

    latestebd := ebdmanv1alpha1.ebd{}
    if err := r.Get(ctx, req.NamespacedName, &amp;latestebd); err != nil {
        return vfmReady, err
    }
    oldCondition := meta.FindStatusCondition(latestebd.Status.Conditions, ebdmanv1alpha1.KubernetesvfmHealthy)

    if err != nil {
        // There was an error checking readiness - Set status to false
        condition.Status = metav1.ConditionFalse
        condition.Reason = ebdmanv1alpha1.ReasonError
        condition.Message = fmt.Sprintf(&quot;Failed to check  vfm readiness: %v&quot;, err)
    } else if vfmReady {
        // The vfm is ready - Set status to true
        condition.Status = metav1.ConditionTrue
        condition.Reason = ebdmanv1alpha1.ReasonReady
        condition.Message = &quot;vfm custom resource is ready&quot;
    } else {
        // The vfm is not ready - Set status to false
        condition.Status = metav1.ConditionFalse
        condition.Reason = ebdmanv1alpha1.ResourceProgressing
        condition.Message = &quot;vfm custom resource is not ready&quot;
    }

    // Only update the LastTransitionTime if the status has changed
    if oldCondition == nil || oldCondition.Status != condition.Status {
        condition.LastTransitionTime = metav1.Now()
    } else {
        condition.LastTransitionTime = oldCondition.LastTransitionTime
    }

    meta.SetStatusCondition(&amp;latestebd.Status.Conditions, condition)

    if oldCondition != nil &amp;&amp; condition.Status == oldCondition.Status &amp;&amp; condition.Reason == oldCondition.Reason &amp;&amp; condition.Message == oldCondition.Message {
        return vfmReady, nil
    }

    // Retry on conflict
    retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
        // Retrieve the latest version of ebd before attempting update
        // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver
        if getErr := r.Get(ctx, req.NamespacedName, &amp;latestebd); getErr != nil {
            return getErr
        }
        if updateErr := r.Status().Update(ctx, &amp;latestebd); updateErr != nil {
            return updateErr
        }
        return nil
    })

    if retryErr != nil {
        r.Log.Error(retryErr, &quot;Failed to update vfm status after retries&quot;)
        return vfmReady, retryErr
    }

    return vfmReady, nil
}
</code></pre>
<p>In this updated version:</p>
<ul>
<li><p>The <code>LastTransitionTime</code> field is updated only when the condition's status changes. This will ensure that the <code>LastTransitionTime</code> accurately reflects when the status was last changed rather than when the <code>checkHealth</code> function was last run. This should provide a more accurate timeline of when the resource's status actually changed, rather than when the reconciliation loop was run.</p>
</li>
<li><p>A retry mechanism is added using <code>retry.RetryOnConflict</code> to re-attempt the status update when a conflict error occurs. Note that you'll need to import the <a href="https://pkg.go.dev/k8s.io/client-go/util/retry" rel="nofollow noreferrer">&quot;<code>k8s.io/client-go/util/retry</code>&quot; package</a> for this.<br />
This is a common pattern for dealing with the <code>Operation cannot be fulfilled...</code> error.</p>
</li>
</ul>
<p>These changes should help to address the issues you were facing with updating the status and conditions of your Kubernetes resources.<br />
Remember that you may still occasionally get conflict errors, especially if there are other clients updating the same object. In these cases, the <code>RetryOnConflict</code> function will retry the update with the latest version of the object.</p>
