mirror of https://github.com/crossplane/docs.git
Add developer-focused documentation for functions
This consists of two sections in the existing page: * How to write a function * How functions work The former is somewhat light, because I intend to add detailed guides for each language we support. It's hard to go too deep in this general documentation without using language-specific examples. Signed-off-by: Nic Cope <nicc@rk0n.org>
This commit is contained in:
parent
cb1f9d2f9c
commit
8c7231eb73
|
@ -303,6 +303,315 @@ See the [Crossplane CLI docs](https://github.com/crossplane/docs/pull/584) to
|
|||
learn how to install and use the Crossplane CLI.
|
||||
{{< /hint >}}
|
||||
|
||||
## Write a composition function
|
||||
|
||||
Composition functions let you replace complicated Compositions with code written
|
||||
in your programming language of choice. Crossplane has tools, software
|
||||
development kits (SDKs) and templates to help you write a function.
|
||||
|
||||
|
||||
<!-- vale write-good.Passive = NO -->
|
||||
Here's an example of a tiny, hello world function. This example is written in
|
||||
Go.
|
||||
<!-- vale write-good.Passive = YES -->
|
||||
|
||||
```go
|
||||
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
|
||||
rsp := response.To(req, response.DefaultTTL)
|
||||
response.Normal(rsp, "Hello world!")
|
||||
return rsp, nil
|
||||
}
|
||||
```
|
||||
|
||||
Some people design composition functions for you to use them with any kind of
|
||||
composite resource. Function Patch and Transform and Function Auto Ready work
|
||||
with any kind of composite resource.
|
||||
|
||||
Another common pattern is to write a composition function specific to one kind
|
||||
of composite resource. The function contains all the logic needed to tell
|
||||
Crossplane what resources to create when you create a composite resource. When
|
||||
you write a composition function like this, your Composition can be small. It
|
||||
just tells Crossplane what function to run when you create, update, or delete a
|
||||
composite resource.
|
||||
|
||||
This Composition tells Crossplane to call {{<hover label="dedicated"
|
||||
line="13">}}function-xr-xbucket{{</hover>}} whenever you create, update, or
|
||||
delete an {{<hover label="dedicated" line="8">}}XBucket{{</hover>}} composite
|
||||
resource. `function-xr-xbucket` is hard coded to handle `XBucket` composite
|
||||
resources.
|
||||
|
||||
```yaml {label="dedicated"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: example-bucket-function
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBucket
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: handle-xbucket-xr
|
||||
functionRef:
|
||||
name: function-xr-xbucket
|
||||
```
|
||||
|
||||
To write a composition function, you:
|
||||
|
||||
1. Create the function from a template.
|
||||
1. Edit the template to add your function's logic.
|
||||
1. Test your function.
|
||||
1. Build your function, and push it to a package registry.
|
||||
|
||||
You use the [Crossplane CLI](https://github.com/crossplane/docs/pull/584) to
|
||||
create, test, build, and push a function. For example,
|
||||
|
||||
```shell {copy-lines=none}
|
||||
# Create the function from a template.
|
||||
crossplane beta xpkg init function-example function-template-go
|
||||
Initialized package "function-example" in directory "/home/negz/control/negz/function-example" from https://github.com/crossplane/function-template-go/tree/91a1a5eed21964ff98966d72cc6db6f089ad63f4 (main)
|
||||
|
||||
$ ls
|
||||
Dockerfile fn.go fn_test.go go.mod go.sum input LICENSE main.go package README.md renovate.json
|
||||
|
||||
# Edit the template to add your function's logic
|
||||
$ vim fn.go
|
||||
|
||||
# Build the function.
|
||||
$ docker build . --quiet --tag runtime
|
||||
sha256:2c31b0f7a34b34ba5b0b2dacc94c360d18aca1b99f56ca4f40a1f26535a7c1c4
|
||||
|
||||
# Package the function.
|
||||
$ crossplane xpkg build -f package --embed-runtime-image=runtime
|
||||
|
||||
# Push the function package to xpkg.upbound.io.
|
||||
$ crossplane xpkg push -f package/*.xpkg crossplane-contrib/function-example:v0.1.0
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
Crossplane has
|
||||
[language specific guides]({{<ref "../../knowledge-base/guides">}}) to writing
|
||||
a composition function. Refer to the guide for your preferred language for a
|
||||
more detailed guide to writing a function.
|
||||
{{</hint>}}
|
||||
|
||||
When you're writing a composition function it's useful to know how composition
|
||||
functions work. Read the next section to learn
|
||||
[how composition functions work](#how-composition-functions-work).
|
||||
|
||||
## How composition functions work
|
||||
|
||||
Each composition function is actually a [gRPC](https://grpc.io) server. gRPC is
|
||||
a high performance, open source remote procedure call (RPC) framework. When you
|
||||
[install a function](#install-a-composition-function) Crossplane deploys the
|
||||
function as a gRPC server. Crossplane encrypts and authenticates all gRPC
|
||||
communication.
|
||||
|
||||
You don't have to be a gRPC expert to write a function. Crossplane's function
|
||||
SDKs setup gRPC for you. It's useful to understand how Crossplane calls your
|
||||
function though, and how your function should respond.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
User->>+API Server: Create composite resource
|
||||
Crossplane Pod->>+API Server: Observe composite resource
|
||||
Crossplane Pod->>+Function Pod: gRPC RunFunctionRequest
|
||||
Function Pod->>+Crossplane Pod: gRPC RunFunctionResponse
|
||||
Crossplane Pod->>+API Server: Apply desired composed resources
|
||||
```
|
||||
|
||||
When you create, update, or delete a composite resource that uses composition
|
||||
functions Crossplane calls each function in the order they appear in the
|
||||
Composition's pipeline. Crossplane calls each function by sending it a gRPC
|
||||
RunFunctionRequest. The function must respond with a gRPC RunFunctionResponse.
|
||||
|
||||
{{<hint "tip">}}
|
||||
You can find detailed schemas for the RunFunctionRequest and RunFunctionResponse
|
||||
RPCs in the [Buf Schema Registry](https://buf.build/crossplane/crossplane/docs/main:apiextensions.fn.proto.v1beta1).
|
||||
{{</hint>}}
|
||||
|
||||
When Crossplane calls a function it includes four important things in the
|
||||
RunFunctionRequest.
|
||||
|
||||
1. The __observed state__ of the composite resource, and any composed resources.
|
||||
1. The __desired state__ of the composite resource, and any composed resources.
|
||||
1. The function's __input__.
|
||||
1. The function pipeline's __context__.
|
||||
|
||||
A function's main job is to update the __desired state__ and return it to
|
||||
Crossplane. It does this by returning a RunFunctionResponse.
|
||||
|
||||
Most composition functions read the observed state of the composite resource,
|
||||
and use it to add composed resources to the desired state. This tells Crossplane
|
||||
which composed resources it should create or update.
|
||||
|
||||
{{<hint "tip">}}
|
||||
<!-- vale write-good.Weasel = NO -->
|
||||
<!-- Disable Weasel to say "usually", which is correct in this context. -->
|
||||
A _composed_ resource is a resource created by a composite resource. Composed
|
||||
resources are usually Crossplane managed resources (MRs), but they can be any
|
||||
kind of Crossplane resource. For example a composite resource could also create
|
||||
a ProviderConfig, or another kind of composite resource.
|
||||
<!-- vale write-good.Weasel = YES -->
|
||||
{{</hint>}}
|
||||
|
||||
### Observed state
|
||||
|
||||
When you create a composite resource like this one, Crossplane _observes_ it and
|
||||
sends it to the composition function as part of the observed state.
|
||||
|
||||
```yaml
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBucket
|
||||
metadata:
|
||||
name: example-render
|
||||
spec:
|
||||
bucketRegion: us-east-2
|
||||
```
|
||||
|
||||
If any composed resources already exist, Crossplane observes them and sends them
|
||||
to your function to as part of the observed state.
|
||||
|
||||
Crossplane also observes the connection details of your composite resource and
|
||||
any composed resources. It sends them to your function as part of the observed
|
||||
state.
|
||||
|
||||
Crossplane observes the composite resource and any composed resources once,
|
||||
right before it starts calling the functions in the pipeline. This means that
|
||||
Crossplane sends every function in the pipeline the same observed state.
|
||||
|
||||
### Desired state
|
||||
|
||||
Desired state is the set of the changes the function pipeline wants to make to
|
||||
the composite resource and any composed resources. When a function adds composed
|
||||
resources to the desired state Crossplane creates them.
|
||||
|
||||
A function can change:
|
||||
|
||||
* The `status` of the composite resource.
|
||||
* The `metadata` and `spec` of any composed resource.
|
||||
|
||||
A function can also change the connection details and readiness of the composite
|
||||
resource. A function indicates that the composite resource is ready by telling
|
||||
Crossplane whether its composed resources are ready. When the function pipeline
|
||||
tells Crossplane that all composed resources are ready, Crossplane marks the
|
||||
composite resource as ready.
|
||||
|
||||
A function can't change:
|
||||
|
||||
* The `metadata` or `spec` of the composite resource.
|
||||
* The `status` of any composed resource.
|
||||
* The connection details of any composed resource.
|
||||
|
||||
A pipeline of functions _accumulates_ desired state. This means that each
|
||||
function builds upon the desired state of previous functions in the pipeline.
|
||||
Crossplane sends a function the desired state accumulated by all previous
|
||||
functions in the pipeline. The function adds to or updates the desired state and
|
||||
then passes it on. When the last function in the pipeline has run, Crossplane
|
||||
applies the desired state it returns.
|
||||
|
||||
{{<hint "important">}}
|
||||
A function __must__ copy all desired state from its RunFunctionRequest to its
|
||||
RunFunctionResponse. If a function adds a resource to its desired state the next
|
||||
function must copy it to its desired state. If it doesn't, Crossplane doesn't
|
||||
apply the resource. If the resource exists, Crossplane deletes it.
|
||||
|
||||
A function can _intentionally_ choose not to copy parts of the desired state.
|
||||
For example a function may choose not to copy a desired resource to prevent that
|
||||
resource from existing.
|
||||
|
||||
Most function SDKs handle copying desired state automatically.
|
||||
{{</hint>}}
|
||||
|
||||
A function should only add the fields it cares about to the desired state. It
|
||||
should add these fields every time Crossplane calls it. If a function adds a
|
||||
field to the desired state once, but doesn't add it the next time it's called,
|
||||
Crossplane deletes the field. The same is true for composed resources. If a
|
||||
function adds a composed resource to the desired state, but doesn't add it the
|
||||
next time it's called, Crossplane deletes the composed resource.
|
||||
|
||||
{{<hint "tip">}}
|
||||
Crossplane uses
|
||||
[server side apply](https://kubernetes.io/docs/reference/using-api/apply/)
|
||||
to apply the desired state returned by a function pipeline. In server side apply
|
||||
terminology, the desired state is a _fully specified intent_.
|
||||
{{</hint>}}
|
||||
|
||||
For example, if all a function wants is to make sure an S3 bucket in region
|
||||
`us-east-2` exists, it should add this resource to its desired composed
|
||||
resources.
|
||||
|
||||
```yaml
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
```
|
||||
|
||||
Even if the Bucket already exists and has other `spec` fields, or a `status`,
|
||||
`name`, `labels`, etc the function should omit them. The function should only
|
||||
include the fields it has an opinion about. Crossplane takes care of applying
|
||||
the fields the function cares about, merging them with the existing Bucket.
|
||||
|
||||
{{<hint "tip">}}
|
||||
Composition functions don't actually use YAML for desired and observed
|
||||
resources. This example uses YAML for illustration purposes only.
|
||||
{{</hint>}}
|
||||
|
||||
### Function input
|
||||
|
||||
If a Composition includes {{<hover label="input" line="14">}}input{{</hover>}}
|
||||
Crossplane sends it to the function. Input is a useful way to provide extra
|
||||
configuration to a function. Supporting input is optional. Not all functions
|
||||
support input.
|
||||
|
||||
```yaml {label="input",copy-lines="none"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: example-render
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBucket
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: patch-and-transform
|
||||
functionRef:
|
||||
name: function-patch-and-transform
|
||||
input:
|
||||
apiVersion: pt.fn.crossplane.io/v1beta1
|
||||
kind: Resources
|
||||
resources:
|
||||
- name: storage-bucket
|
||||
base:
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
patches:
|
||||
- type: FromCompositeFieldPath
|
||||
fromFieldPath: spec.bucketRegion
|
||||
toFieldPath: spec.forProvider.region
|
||||
```
|
||||
|
||||
{{<hint "important">}}
|
||||
Crossplane doesn't validate function input. It's a good idea for a function to
|
||||
validate its own input.
|
||||
{{</hint>}}
|
||||
|
||||
### Function pipeline context
|
||||
|
||||
Sometimes two functions in a pipeline want to share information with each other
|
||||
that isn't desired state. Functions can use context for this. Any function can
|
||||
write to the pipeline context. Crossplane passes the context to all following
|
||||
functions. When Crossplane has called all functions it discards the pipeline
|
||||
context.
|
||||
|
||||
Crossplane can write context too. If you enable the alpha
|
||||
[composition environment]({{<ref "environment-configs">}}) feature Crossplane
|
||||
writes the environment to the top-level context field
|
||||
`apiextensions.crossplane.io/environment`.
|
||||
|
||||
## Disable composition functions
|
||||
|
||||
Crossplane enables composition functions by default. Disable support for
|
||||
|
|
Loading…
Reference in New Issue