Document requirements to make functions with `requirements` backward compatible
- Dominant language
- SCSS
- Stars
- 60
- Forks
- 163
- Avg merge
- 15h 20m
- Merged PRs (30d)
- 9
Description
### What problem are you facing?
While responding to https://github.com/crossplane/crossplane/issues/5399 I realized the new [extra resources](https://github.com/crossplane/crossplane/blob/release-1.15/design/design-doc-composition-functions-extra-resources.md) feature of Crossplane needs some care to be backward compatible.
Let's define:
* Forward compatible: a function built using the Crossplane v1.14 protobuf works with v1.15 and above.
* Backward compatible: a function build using the Crossplane v1.15 protobuf works with v1.14 and earlier.
When I say "works" I mean that all features of the lowest common version work as normal:
* Crossplane v1.15 and a function built against the v1.14 protobuf should work and be able to use all v1.14 features.
* Crossplane v1.14 and a function built against the v1.15 protobuf should work and be able to use all v1.14 features.
The extra resources feature is forward compatible. A function built using the v1.14 protobuf will never set `requirements` in its `RunFunctionResponse`, and will never expect Crossplane to respond with `extra_resources`. Crossplane won't include them, and even if it did the function would just ignore them. This is great, because it means existing functions will automatically work with v1.15.
The feature is _potentially_ not backward compatible. It requires good function behavior to be backward compatible.
A function built using the v1.15 protobuf could set `requirements` to ask for extra resources. When it does this, it expects Crossplane to communicate that resolution of requirements is complete by sending a second `RunFunctionRequest` with the `extra_resources` field set:
```protobuf
message RunFunctionRequest {
// Existing fields omitted for brevity
// Note that extra resources is a map to Resources, plural.
// The map key corresponds to the key in a RunFunctionResponse's
// extra_resources field. If a Function requests extra resources that
// don't exist Crossplane sets the map key to an empty Resources
// message to indicate that it attempted to satisfy the request.
map extra_resources = 3;
}
```
You could imagine functions that want extra resources having a condition like:
* If `extra_resources` not in `RunFunctionRequest`, send `RunFunctionResponse` with `requirements` set.
* Else use `extra_resources` from `RunFunctionRequest` to set `desired` in `RunFunctionResponse`.
Crossplane v1.14 will never set `extra_resources`. So from the function's perspective it's going to look like Crossplane is constantly sending it the initial `RunFunctionRequest` in the resolution process. It will set `requirements` and expect to be called again with `extra_resources`, but that will never happen. Instead Crossplane is going to ignore its `requirements` and process its `desired` state.
### How could Crossplane help solve your problem?
I think as long as functions with `requirements` degrade gracefully if `extra_resources` are never sent, they're backward compatible. https://github.com/crossplane-contrib/function-environment-configs/blob/0c9cf192/fn.go#L71 is a good example of this.
If you try to use `function-environment-configs` with Crossplane v1.14:
1. Crossplane will send a `RunFunctionRequest`.
2. The function will send a `RunFunctionResponse` with `requirements`, but (critically) also pass through any existing `desired` state unmodified (thanks to its use of `response.To`).
3. Crossplane will ignore the `requirements` and process the `desired` state.
This means the function will just be a no-op with Crossplane v1.14. Any subsequent functions will run as if there were no environment configs found.
I think we should explicitly document that a function should:
* Always pass through `desired` in its `RunFunctionResponse` when it returns `requirements`.
* If appropriate, don't wait for `requirements` to be satisfied to return `desired` state.
This avoids two possible mistakes:
* A function not passing through `desired` when it returns `requirements`, because it expects Crossplane to ignore `desired` until `requirements` have been processed. Crossplane v1.14 would interpret this as "delete all composed resources".
* A function with _optional_ `requirements` never updating `desired` until it sees that its `requirements` has been processed. Such a function would always be a no-op with Crossplane v1.14, even though it need not be.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.