# Using Conditions for operator-controller ## Summary _Conditions_ are a common, but not universal, method to report the status of a resource. Other OLM components (e.g. `rukpak`) uses _Conditions_. There's no reason not to use _Conditions_. ## Conditions _Conditions_ are a common, but not universal, method to report the status of a resource. _Conditions_ are usually kept in a list/array and indexed by the **Type**. _Events_ are used to keep the history of a resource; _Conditions_ are used to keep the status of the resource. The following is a minimized version from reference #1. ```go type Condition struct { // type of condition - this is the KEY Type string // status of the condition, one of True, False, Unknown. Status ConditionStatus // observedGeneration represents the .metadata.generation // that the condition was set based upon. ObservedGeneration int64 // lastTransitionTime is the last time the condition transitioned // from one status to another. LastTransitionTime Time // reason contains a programmatic identifier indicating the reason // for the condition's last transition. Reason string // message is a human readable message (reason) indicating details // about the transition. Message string } ``` **ObservedGeneration** and **LastTransitionTime* are calculated values, based on when the _Condition_ was updated. These represent the WHEN. No need to discuss further. **Reason** and **Message** represent the same thing; the first is machine-readable and the second is human-readable. These represent the WHY the resource is in this condition. Error reasons and messages could be used here. So, if a software component has a fairly straightforward error reporting mechanism (e.g. represented by the golang `error` type), that could almost be used directly. Although, we will want to have well-defined values for **Reason**. **Status** indicates whether or not the current _Condition_ as specified by **Type** is active. For example, if **Type** is "Reconciled", then this _Condition_ applies if **Status** is "True", and does not apply when **Status** is "False". This may also have the value of "Unknown" in certain situations. Having a non-"True" value allows an indication that this _Condition_ had occurred when paired with **LastTransitionTime**. Alternatively, without the timestamp, **Status** "False" may indicate the _Condition_ has never occured. **Type** is the meat of the condition. It is the "key" used to merge _Conditions_. Thus, each **Type** can only be in a _Condition_ array once. But the _Condition_ can be updated over time. **Types** are almost like states in a state machine, but not quite. A resource may have multiple active **Types** (represented as **Status** = "True") at the same time, representing the status of that resource. From reference #2: >Condition type names should describe the current observed state of the resource, rather than describing the current state transitions. This typically means that the name should be an adjective ("Ready", "OutOfDisk") or a past-tense verb ("Succeeded", "Failed") rather than a present-tense verb ("Deploying"). Intermediate states may be indicated by setting the status of the condition to Unknown. > >For state transitions which take a long period of time (e.g. more than 1 minute), it is reasonable to treat the transition itself as an observed state. In these cases, the Condition (such as "Resizing") itself should not be transient, and should instead be signalled using the True/False/Unknown pattern. This allows other observers to determine the last update from the controller, whether successful or failed. In cases where the state transition is unable to complete and continued reconciliation is not feasible, the Reason and Message should be used to indicate that the transition failed. # Example The following examples ignore **ObservedGeneration**, **LastTransitionTime**, and **Message**. Here, "Ready" is used as a top-level condition representing the overall state. This might be the conditions of an operator that successfully loaded: ```yaml - type: Reconciled status: True reason: ReconcileSuccess - type: Downloaded status: True reason: DownloadSuccess - type: Deployed status: True reason: DeploymentSuccess - type: Ready status: True reason: OperatorRunning ``` This might be the conditions of an operator that is cycling through reconciliation, and never was installed: ```yaml - type: Reconciled status: False reason: OperatorNotFoundInCatalog - type: Ready status: False reason: NotPresentOnSystem ``` This might be the conditions of an operator that can’t be downloaded, but is attempting to: ```yaml - type: Reconciled status: True reason: ReconcileSuccess - type: Downloaded status: False reason: DnsResolutionFailing - type: Ready status: False reason: NotPresentOnSystem ``` ## Other OLM Components ### deppy Deppy returns the golang `error` type (i.e. `string`) from API calls. Additional information is output via `fmt.Printf()` to the log. These `error` returns could be used directly if they were camelCase. It might be desirable to return a camelCase string for `error` to be used directly with **Reason**, and an API to get a human-readable **Message**. ### rukpak Rukpak implements its own API, and will not be included within `operator-controller`. Rukpak also uses the _Condition_ type in its API. ## References 1. https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Condition 1. [Kubernetes API conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties) 1. [What the heck are Conditions in Kubernetes controllers?](https://maelvls.dev/kubernetes-conditions/)