Project: Crossplane (Orchestration & Management / Scheduling & Orchestration)
File: one-pager-fn-claim-conditions.md

Owner: Dalton Hill (@dalton-hill-0) Reviewers: Nic Cope (@negz) Status: Draft Composition Function authors should be able to communicate and translate the underlying status with users. We think authors often won't want to surface the status as it appear on an MR, but will probably want to derive more user-friendly messages from it. Messages that are more meaningful to folks reading claims. Some examples include: The external system for an MR is unreachable. The MR is incorrectly configured. The MR is being created, updated, etc. We think authors may want to have a catch-all Internal Error message. Authors should be able to display the real error on the XR and provide a basic "Internal Error" message on the Claim. Currently internal errors often leave the Claim in a "Waiting" state. It would be nice to notify the user that an internal error was encountered, and that the team has been notified by an alert. Currently functions can return Results. Depending on the type of results seen, you can observe the following behavior on the Composite Resource. Fatal Result: Synced status condition is set to False, contains result's message. Warning Event generated (reason: ReconcileError), containing result's message. Warning Result: Warning Event (reason: ComposeResources) generated, containing result's message. Normal Result: Normal Event (reason: ComposeResources) generated, containing result's message. Currently the only path to communicate a custom message with the user is by defining your own field in the Claim's status. For example, we can define an XRD with: ```yaml status: someCommunicationField: msg: "Something went wrong." ``` There are a couple issues with this solution. If we need to halt resource reconciliation due to a fatal error, we can do so with the 's `response.Fatal`, however, this does not also allow us to update the XR and Claim for communication with the user. There is an existing field that would be more intuitive to use as it is already performing this same task for Crossplane itself (`status.conditions`). Currently you can update the Composite's status conditions by setting them with SetDesiredCompositeResource. There are a couple of limitations to this: it only shows up on the XR it only shows up if there are no fatal results Example of setting the Composite's status conditions. ```go // includes: // corev1 "k8s.io/api/core/v1" // metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" // // "github.com/crossplane/function-sdk-go/response" desiredXR, err := request.GetDesiredCompositeResource(req) c := xpv1.Condition{ Type: xpv1.ConditionType("ImageReady"), Status: corev1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: "NotFound", Message: "The image provided does not exist or you are not "+ "authorized to use it.", } desiredXR.Resource.SetConditions(c) response.SetDesiredCompositeResource(rsp, desiredXR) ``` We would like to allow the Composition Function author to: Choose where results go (Claim or XR) Allow results to update the Status Conditions of the XR and Claim The following sections get into the details of each of the above items. Currently each result returned by a function will create a corresponding event on the XR (if no previous fatal result exists). We can expand this functionality by allowing the Result to have targets. In order to accomplish this, we will need to expand the Result API as follows. ```protobuf message Result { // Omitted for brevity Target target = 3; } // Target of Function results. enum Target { TARGET_UNSPECIFIED = 0; TARGETCOMPOSITEONLY = 1; TARGETCOMPOSITEAND_CLAIM = 2; } ``` The reason for having `TARGETCOMPOSITEANDCLAIM` and not `TARGETCLAIM` is an implementation

This prevents more involved API changes, and this is also consistent with existing behavior (func copies to XR, Crossplane copies XR to Claim). The following is an example of how a function author could use this behavior. Note that this is just a sketch and may not be the final API. ```go // import "github.com/crossplane/function-sdk-go/response" response.Fatal(rsp, errors.New("The image provided does not exist or you are not authorized to use it.")). ConditionFalse("ImageReady", "NotFound"). TargetCompositeAndClaim() ``` To support this behavior, the status of the Composite would need an additional field `claimConditions`. This field will contain the types of conditions that should be propagated to the Claim. ```yaml status: claimConditions: [DatabaseReady, ImageReady] conditions: type: DatabaseReady status: True reason: Available type: ImageReady status: False reason: NotFound message: The image provided does not exist or you are not authorized to use it. ``` We would like the function author to be able to set the Claim's status conditions. This would allow the function author to clearly communicate the state of the Claim with their users. To allow the setting of conditions in the result, we will need to expand the Result API as follows. ```protobuf message Result { // Omitted for brevity // Optionally update the supplied status condition on all targets. // The result's reason and message will be used in the condition. optional Condition condition = 4; } message Condition { // Type of the condition, e.g. DatabaseReady. // 'Ready' and 'Synced' are reserved for use by Crossplane. string type = 1; // Status of the condition. Status status = 2; // Machine-readable PascalCase reason. string reason = 3; } ``` An example of a function utilizing this new ability: ```go // rb "github.com/crossplane/function-sdk-go/response/result/builder" // const databaseReady = "DatabaseReady" // const reasonUnauthorized = "Unauthorized" // var messageUnauthorized = errors.New("You are unauthorized to access this resource.") result := rb.Fatal(messageUnauthorized). TargetCompositeAndClaim(). WithConditionFalse(databaseReady, reasonUnauthorized). Build() response.AddResult(rsp, result) ``` Lets say we are a team of platform engineers who have a Crossplane offering. For each Claim, we wish to expose a set of conditions that users can expect to exist which provide: the current status of the underlying resources any steps required by the user to remediate an issue Lets say we have a claim that does the following.. Accepts an identifier to an existing database Accepts an image to deploy Configures a deployment that uses the image provided and is authenticated to the database. Given a few different scenarios, users could expect to see the following `status.conditions` for the claim. First we found the database and determined that the user has authorization, however, the image they provided was not found. An example of the Claim's status: ```yaml status: conditions: type: DatabaseReady status: True reason: Available type: ImageReady status: False reason: NotFound message: The image provided does not exist or you are not authorized to use it. ``` All is fine and the application is progressing but not yet fully online. An example of the Claim's status: ```yaml status: conditions: type: DatabaseReady status: True reason: Available type: ImageReady status: True reason: Available type: AppReady status: False reason: Creating message: Waiting for the deployment to be available. ``` Once everything is online and running smoothly, users should see something like this. An example of the Claim's status: ```yaml status: conditions: type: DatabaseReady status: True reason: Available type: ImageReady status: True reason: Available type: AppReady status: True reason: Available ```