Composite Resources
This document applies to Crossplane version v1.12 and not to the latest release v1.13.
Crossplane Composite Resources are opinionated Kubernetes Custom Resources that are composed of Managed Resources. We often call them XRs for short.
Composite Resources are designed to let you build your own platform with your own opinionated concepts and APIs without needing to write a Kubernetes controller from scratch. Instead, you define the schema of your XR and teach Crossplane which Managed Resources it should compose (i.e. create) when someone creates the XR you defined.
If you’re already familiar with Composite Resources and looking for a detailed configuration reference or some tips, tricks, and troubleshooting information, try the Composition Reference.
Below is an example of a Composite Resource:
1apiVersion: database.example.org/v1alpha1
2kind: XPostgreSQLInstance
3metadata:
4 name: my-db
5spec:
6 parameters:
7 storageGB: 20
8 compositionRef:
9 name: production
10 writeConnectionSecretToRef:
11 namespace: crossplane-system
12 name: my-db-connection-details
You define your own XRs, so they can be of whatever API version and kind you like, and contain whatever spec and status fields you need.
How It Works
The first step towards using Composite Resources is configuring Crossplane so
that it knows what XRs you’d like to exist, and what to do when someone creates
one of those XRs. This is done using a CompositeResourceDefinition
(XRD)
resource and one or more Composition
resources.
Once you’ve configured Crossplane with the details of your new XR you can either create one directly, or use a claim. Typically only the folks responsible for configuring Crossplane (often a platform or SRE team) have permission to create XRs directly. Everyone else manages XRs via a lightweight proxy resource called a Composite Resource Claim (or claim for short). More on that later.
If you’re coming from the Terraform world you can think of an XRD as similar to the
variable
blocks of a Terraform module, while theComposition
is the rest of the module’s HCL code that describes how to use those variables to create a bunch of resources. In this analogy the XR or claim is a little like atfvars
file providing inputs to the module.
Defining Composite Resources
A CompositeResourceDefinition
(or XRD) defines the type and schema of your XR.
It lets Crossplane know that you want a particular kind of XR to exist, and what
fields that XR should have. An XRD is a little like a CustomResourceDefinition
(CRD), but slightly more opinionated. Writing an XRD is mostly a matter of
specifying an OpenAPI “structural schema”.
The XRD that defines the XPostgreSQLInstance
XR above would look like this:
1apiVersion: apiextensions.crossplane.io/v1
2kind: CompositeResourceDefinition
3metadata:
4 name: xpostgresqlinstances.database.example.org
5spec:
6 group: database.example.org
7 names:
8 kind: XPostgreSQLInstance
9 plural: xpostgresqlinstances
10 claimNames:
11 kind: PostgreSQLInstance
12 plural: postgresqlinstances
13 versions:
14 - name: v1alpha1
15 served: true
16 referenceable: true
17 schema:
18 openAPIV3Schema:
19 type: object
20 properties:
21 spec:
22 type: object
23 properties:
24 parameters:
25 type: object
26 properties:
27 storageGB:
28 type: integer
29 required:
30 - storageGB
31 required:
32 - parameters
You might notice that the XPostgreSQLInstance
example above has some fields
that don’t appear in the XRD, like the writeConnectionSecretToRef
and
compositionRef
fields. This is because Crossplane automatically injects some
standard Crossplane Resource Model (XRM) fields into all XRs.
Configuring Composition
A Composition
lets Crossplane know what to do when someone creates a Composite
Resource. Each Composition
creates a link between an XR and a set of one or
more Managed Resources - when the XR is created, updated, or deleted the set of
Managed Resources are created, updated or deleted accordingly.
You can add multiple Compositions for each XRD, and choose which should be used when XRs are created. This allows a Composition to act like a class of service - for example you could configure one Composition for each environment you support, such as production, staging, and development.
A basic Composition
for the above XPostgreSQLInstance
might look like this:
1apiVersion: apiextensions.crossplane.io/v1
2kind: Composition
3metadata:
4 name: example
5 labels:
6 crossplane.io/xrd: xpostgresqlinstances.database.example.org
7 provider: gcp
8spec:
9 writeConnectionSecretsToNamespace: crossplane-system
10 compositeTypeRef:
11 apiVersion: database.example.org/v1alpha1
12 kind: XPostgreSQLInstance
13 resources:
14 - name: cloudsqlinstance
15 base:
16 apiVersion: database.gcp.crossplane.io/v1beta1
17 kind: CloudSQLInstance
18 spec:
19 forProvider:
20 databaseVersion: POSTGRES_12
21 region: us-central1
22 settings:
23 tier: db-custom-1-3840
24 dataDiskType: PD_SSD
25 ipConfiguration:
26 ipv4Enabled: true
27 authorizedNetworks:
28 - value: "0.0.0.0/0"
29 patches:
30 - type: FromCompositeFieldPath
31 fromFieldPath: spec.parameters.storageGB
32 toFieldPath: spec.forProvider.settings.dataDiskSizeGb
The above Composition
tells Crossplane that when someone creates an
XPostgreSQLInstance
XR Crossplane should create a CloudSQLInstance
in
response. The storageGB
field of the XPostgreSQLInstance
should be used to
configure the dataDiskSizeGb
field of the CloudSQLInstance
. This is only a
small subset of the functionality a Composition
enables - take a look at the
reference page to learn more.
We almost always talk about XRs composing Managed Resources, but actually an XR can also compose other XRs to allow nested layers of abstraction. XRs don’t support composing arbitrary Kubernetes resources (e.g. Deployments, operators, etc) directly but you can do so using our Kubernetes and Helm providers.
Claiming Composite Resources
Crossplane uses Composite Resource Claims (or just claims, for short) to allow application operators to provision and manage XRs. When we talk about using XRs it’s typically implied that the XR is being used via a claim. Claims are almost identical to their corresponding XRs. It helps to think of a claim as an application team’s interface to an XR. You could also think of claims as the public (app team) facing part of the opinionated platform API, while XRs are the private (platform team) facing part.
A claim for the XPostgreSQLInstance
XR above would look like this:
1apiVersion: database.example.org/v1alpha1
2kind: PostgreSQLInstance
3metadata:
4 namespace: default
5 name: my-db
6spec:
7 parameters:
8 storageGB: 20
9 compositionRef:
10 name: production
11 writeConnectionSecretToRef:
12 name: my-db-connection-details
There are three key differences between an XR and a claim:
- Claims are namespaced, while XRs (and Managed Resources) are cluster scoped.
- Claims are of a different
kind
than the XR - by convention the XR’skind
without the proceedingX
. For example aPostgreSQLInstance
claims anXPostgreSQLInstance
. - An active claim contains a reference to its corresponding XR, while an XR contains both a reference to the claim an array of references to the managed resources it composes.
Not all XRs offer a claim - doing so is optional. See the XRD section of the Composition reference to learn how to offer a claim.
Claims may seem a little superfluous at first, but they enable some handy scenarios, including:
Private XRs. Sometimes a platform team might not want a type of XR to be directly consumed by their application teams. For example because the XR represents ‘supporting’ infrastructure - consider the above VPC
XNetwork
XR. App teams might createPostgreSQLInstance
claims that reference (i.e. consume) anXNetwork
, but they shouldn’t be creating their own. Similarly, some kinds of XR might be intended only for ’nested’ use - intended only to be composed by other XRs.Global XRs. Not all infrastructure is conceptually namespaced. Say your organisation uses team scoped namespaces. A
PostgreSQLInstance
that belongs to Team A should probably be part of theteam-a
namespace - you’d represent this by creating aPostgreSQLInstance
claim in that namespace. On the other hand theXNetwork
XR we mentioned previously could be referenced (i.e. used) by XRs from many different namespaces - it doesn’t exist to serve a particular team.Pre-provisioned XRs. Finally, separating claims from XRs allows a platform team to pre-provision certain kinds of XR. Typically an XR is created on-demand in response to the creation of a claim, but it’s also possible for a claim to instead request an existing XR. This can allow application teams to instantly claim infrastructure like database instances that would otherwise take minutes to provision on-demand.
This reference provides detailed examples of defining, configuring, and using Composite Resources in Crossplane. You can also refer to Crossplane’s [API documentation][api-docs] for more details. If you’re looking for a more general overview of Composite Resources and Composition in Crossplane, try the [Composite Resources][xr-concepts] page under Concepts.
Composite Resources and Claims
The type and most of the schema of Composite Resources and claims are largely of your own choosing, but there is some common ‘machinery’ injected into them. Here’s a hypothetical XR that doesn’t have any user-defined fields and thus only includes the automatically injected Crossplane machinery:
1apiVersion: database.example.org/v1alpha1
2kind: XPostgreSQLInstance
3metadata:
4 # This XR was created automatically by a claim, so its name is derived from
5 # the claim's name.
6 name: my-db-mfd1b
7 annotations:
8 # The external name annotation has special meaning in Crossplane. When a
9 # claim creates an XR its external name will automatically be propagated to
10 # the XR. Whether and how the external name is propagated to the resources
11 # the XR composes is up to its Composition.
12 crossplane.io/external-name: production-db-0
13spec:
14 # XRs have a reference to the claim that created them (or, if the XR was
15 # pre-provisioned, to the claim that later claimed them).
16 claimRef:
17 apiVersion: database.example.org/v1alpha1
18 kind: PostgreSQLInstance
19 name: my-db
20 # The compositeDeletePolicy specifies the propagation policy that will be used by Crossplane
21 # when deleting the Composite Resource that is associated with the Claim. The default
22 # value is Background, which causes the Composite resource to be deleted using
23 # the kubernetes default propagation policy of Background, and all associated
24 # resources will be deleted simultaneously. The other value for this field is Foreground,
25 # which will cause the Composite resource to be deleted using Foreground Cascading Deletion.
26 # Kubernetes will add a foregroundDeletion finalizer to all of the resources in the
27 # dependency graph, and they will be deleted starting with the edge or leaf nodes and
28 # working back towards the root Composite. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#cascading-deletion
29 # for more information on cascading deletion.
30 compositeDeletePolicy: Background
31 # The compositionRef specifies which Composition this XR will use to compose
32 # resources when it is created, updated, or deleted. This can be omitted and
33 # will be set automatically if the XRD has a default or enforced composition
34 # reference, or if the below composition selector is set.
35 compositionRef:
36 name: production-us-east
37 # The compositionSelector allows you to match a Composition by labels rather
38 # than naming one explicitly. It is used to set the compositionRef if none is
39 # specified explicitly.
40 compositionSelector:
41 matchLabels:
42 environment: production
43 region: us-east
44 provider: gcp
45 # The environment is an in-memory object that can be patched from / to during
46 # rendering.
47 # The environment is composed by merging the 'data' of all EnvironmentConfigs
48 # referenced below. It is disposed after every reconcile.
49 # NOTE: EnvironmentConfigs are an alpha feature and need to be enabled with
50 # the '--enable-environment-configs' flag on startup.
51 environment:
52 # EnvironmentConfigs is a list of object references that is made up of
53 # name references and label selectors
54 environmentConfigs:
55 - type: Reference # this is the default
56 ref:
57 name: example-environment
58 - type: Selector
59 selector:
60 - key: stage
61 type: FromCompositeFieldPath # this is the default
62 valueFromFieldPath: spec.parameters.stage
63 - key: provider
64 type: Value
65 value: "gcp"
66 # The resourceRefs array contains references to all of the resources of which
67 # this XR is composed. Despite being in spec this field isn't intended to be
68 # configured by humans - Crossplane will take care of keeping it updated.
69 resourceRefs:
70 - apiVersion: database.gcp.crossplane.io/v1beta1
71 kind: CloudSQLInstance
72 name: my-db-mfd1b-md9ab
73 # The writeConnectionSecretToRef field specifies a Kubernetes Secret that this
74 # XR should write its connection details (if any) to.
75 writeConnectionSecretToRef:
76 namespace: crossplane-system
77 name: my-db-connection-details
78status:
79 # An XR's 'Ready' condition will become True when all of the resources it
80 # composes are deemed ready. Refer to the Composition 'readinessChecks' field
81 # for more information.
82 conditions:
83 - type: Ready
84 statue: "True"
85 reason: Available
86 lastTransitionTime: 2021-10-02T07:20:50.52Z
87 # The last time the XR published its connection details to a Secret.
88 connectionDetails:
89 lastPublishedTime: 2021-10-02T07:20:51.24Z
Similarly, here’s an example of the claim that corresponds to the above XR:
1apiVersion: database.example.org/v1alpha1
2kind: PostgreSQLInstance
3metadata:
4 # Claims are namespaced, unlike XRs.
5 namespace: default
6 name: my-db
7 annotations:
8 # The external name annotation has special meaning in Crossplane. When a
9 # claim creates an XR its external name will automatically be propagated to
10 # the XR. Whether and how the external name is propagated to the resources
11 # the XR composes is up to its Composition.
12 crossplane.io/external-name: production-db-0
13spec:
14 # The resourceRef field references the XR this claim corresponds to. You can
15 # either set it to an existing (compatible) XR that you'd like to claim or
16 # (the more common approach) leave it blank and let Crossplane automatically
17 # create and reference an XR for you.
18 resourceRef:
19 apiVersion: database.example.org/v1alpha1
20 kind: XPostgreSQLInstance
21 name: my-db-mfd1b
22 # A claim's compositionRef and compositionSelector work the same way as an XR.
23 compositionRef:
24 name: production-us-east
25 compositionSelector:
26 matchLabels:
27 environment: production
28 region: us-east
29 provider: gcp
30 # A claim's writeConnectionSecretToRef mostly works the same way as an XR's.
31 # The one difference is that the Secret is always written to the namespace of
32 # the claim.
33 writeConnectionSecretToRef:
34 name: my-db-connection-details
35status:
36 # A claim's 'Ready' condition will become True when its XR's 'Ready' condition
37 # becomes True.
38 conditions:
39 - type: Ready
40 statue: "True"
41 reason: Available
42 lastTransitionTime: 2021-10-02T07:20:50.52Z
43 # The last time the claim published its connection details to a Secret.
44 connectionDetails:
45 lastPublishedTime: 2021-10-02T07:20:51.24Z
If your XR or claim isn’t working as you’d expect you can try running
kubectl describe
against it for details - pay particular attention to any events and status conditions. You may need to follow the references from claim to XR to composed resources to find out what’s happening.
CompositeResourceDefinitions
Below is an example CompositeResourceDefinition
that includes all configurable
fields.
1apiVersion: apiextensions.crossplane.io/v1
2kind: CompositeResourceDefinition
3metadata:
4 # XRDs must be named '<plural>.<group>', per the plural and group names below.
5 name: xpostgresqlinstances.example.org
6spec:
7 # This XRD defines an XR in the 'example.org' API group.
8 group: example.org
9 # The kind of this XR will be 'XPostgreSQLInstance`. You may also optionally
10 # specify a singular name and a listKind.
11 names:
12 kind: XPostgreSQLInstance
13 plural: xpostgresqlinstances
14 # This type of XR offers a claim. Omit claimNames if you don't want to do so.
15 # The claimNames must be different from the names above - a common convention
16 # is that names are prefixed with 'X' while claim names are not. This lets app
17 # team members think of creating a claim as (e.g.) 'creating a
18 # PostgreSQLInstance'.
19 claimNames:
20 kind: PostgreSQLInstance
21 plural: postgresqlinstances
22 # Each type of XR can declare any keys they write to their connection secret
23 # which will act as a filter during aggregation of the connection secret from
24 # composed resources. It's recommended to provide the set of keys here so that
25 # consumers of claims and XRs can see what to expect in the connection secret.
26 # If no key is given, then all keys in the aggregated connection secret will
27 # be written to the connection secret of the XR.
28 connectionSecretKeys:
29 - hostname
30 # Each type of XR may specify a default Composite Delete Policy to be used
31 # when the Claim has no compositeDeletePolicy. The valid values are Background
32 # and Foreground, and the default is Background. See the description of the
33 # compositeDeletePolicy parameter for more information.
34 defaultCompositeDeletePolicy: Background
35 # Each type of XR may specify a default Composition to be used when none is
36 # specified (e.g. when the XR has no compositionRef or selector). A similar
37 # enforceCompositionRef field also exists to allow XRs to enforce a specific
38 # Composition that should always be used.
39 defaultCompositionRef:
40 name: example
41 # Each type of XR may specify a default Composition Update Policy to be used
42 # when the Claim has no compositionUpdatePolicy. The valid values are Automatic
43 # and Manual and the default is Automatic.
44 defaultCompositionUpdatePolicy: Automatic
45 # Each type of XR may be served at different versions - e.g. v1alpha1, v1beta1
46 # and v1 - simultaneously. Currently Crossplane requires that all versions
47 # have an identical schema, so this is mostly useful to 'promote' a type of XR
48 # from alpha to beta to production ready.
49 versions:
50 - name: v1alpha1
51 # Served specifies that XRs should be served at this version. It can be set
52 # to false to temporarily disable a version, for example to test whether
53 # doing so breaks anything before a version is removed wholesale.
54 served: true
55 # Referenceable denotes the version of a type of XR that Compositions may
56 # use. Only one version may be referenceable.
57 referenceable: true
58 # Schema is an OpenAPI schema just like the one used by Kubernetes CRDs. It
59 # determines what fields your XR and claim will have. Note that Crossplane
60 # will automatically extend with some additional Crossplane machinery.
61 schema:
62 openAPIV3Schema:
63 type: object
64 properties:
65 spec:
66 type: object
67 properties:
68 parameters:
69 type: object
70 properties:
71 storageGB:
72 type: integer
73 required:
74 - storageGB
75 required:
76 - parameters
77 status:
78 type: object
79 properties:
80 address:
81 description: Address of this MySQL server.
82 type: string
Take a look at the Kubernetes CRD documentation for a more detailed guide to writing OpenAPI schemas. Note that the following fields are reserved for Crossplane machinery, and will be ignored if your schema includes them:
spec.resourceRef
spec.resourceRefs
spec.claimRef
spec.writeConnectionSecretToRef
status.conditions
status.connectionDetails
If your
CompositeResourceDefinition
isn’t working as you’d expect you can try runningkubectl describe xrd
for details - pay particular attention to any events and status conditions.
Compositions
You’ll encounter a lot of ‘field paths’ when reading or writing a Composition
.
Field paths reference a field within a Kubernetes object via a simple string
‘path’. API conventions describe the syntax as:
Standard JavaScript syntax for accessing that field, assuming the JSON object was transformed into a JavaScript object, without the leading dot, such as
metadata.name
.
Valid field paths include:
metadata.name
- Thename
field of themetadata
object.spec.containers[0].name
- Thename
field of the 0thcontainers
element.data[.config.yml]
- The.config.yml
field of thedata
object.apiVersion
- TheapiVersion
field of the root object.
While the following are invalid:
.metadata.name
- Leading period.metadata..name
- Double period.metadata.name.
- Trailing period.spec.containers[]
- Empty brackets.spec.containers.[0].name
- Period before open bracket.
Below is a detailed example of a Composition
. While detailed, this example
doesn’t include every patch, transform, connection detail, and readiness check
type. Keep reading below to discover those.
1apiVersion: apiextensions.crossplane.io/v1
2kind: Composition
3metadata:
4 name: example
5 labels:
6 # An optional convention is to include a label of the XRD. This allows
7 # easy discovery of compatible Compositions.
8 crossplane.io/xrd: xpostgresqlinstances.database.example.org
9 # The following label marks this Composition for GCP. This label can
10 # be used in 'compositionSelector' in an XR or Claim.
11 provider: gcp
12spec:
13
14 # Each Composition must declare that it is compatible with a particular type
15 # of Composite Resource using its 'compositeTypeRef' field. The referenced
16 # version must be marked 'referenceable' in the XRD that defines the XR.
17 compositeTypeRef:
18 apiVersion: database.example.org/v1alpha1
19 kind: XPostgreSQLInstance
20
21 # When an XR is created in response to a claim Crossplane needs to know where
22 # it should create the XR's connection secret. This is configured using the
23 # 'writeConnectionSecretsToNamespace' field.
24 writeConnectionSecretsToNamespace: crossplane-system
25
26 # Each Composition must specify at least one composed resource template. In
27 # this case the Composition tells Crossplane that it should create, update, or
28 # delete a CloudSQLInstance whenever someone creates, updates, or deletes an
29 # XPostgresSQLInstance.
30 resources:
31
32 # It's good practice to provide a unique name for each entry. Note that
33 # this identifies the resources entry within the Composition - it's not
34 # the name the CloudSQLInstance. The 'name' field will be required in a
35 # future version of this API.
36 - name: cloudsqlinstance
37
38 # The 'base' template for the CloudSQLInstance Crossplane will create.
39 # You can use the base template to specify fields that never change, or
40 # default values for fields that may optionally be patched over. Bases must
41 # be a valid Crossplane resource - a Managed Resource, Composite Resource,
42 # or a ProviderConfig.
43 base:
44 apiVersion: database.gcp.crossplane.io/v1beta1
45 kind: CloudSQLInstance
46 spec:
47 forProvider:
48 databaseVersion: POSTGRES_12
49 region: us-central1
50 settings:
51 dataDiskType: PD_SSD
52 ipConfiguration:
53 ipv4Enabled: true
54 authorizedNetworks:
55 - value: "0.0.0.0/0"
56
57 # Each resource can optionally specify a set of 'patches' that copy fields
58 # from (or to) the XR.
59 patches:
60 # FromCompositeFieldPath is the default when 'type' is omitted, but it's
61 # good practice to always include the type for readability.
62 - type: FromCompositeFieldPath
63 fromFieldPath: spec.parameters.size
64 toFieldPath: spec.forProvider.settings.tier
65
66 # Each patch can optionally specify one or more 'transforms', which
67 # transform the 'from' field's value before applying it to the 'to' field.
68 # Transforms are applied in the order they are specified; each transform's
69 # output is passed to the following transform's input.
70 transforms:
71 - type: map
72 map:
73 medium: db-custom-1-3840
74
75 policy:
76 # By default a patch from a field path that does not exist is simply
77 # skipped until it does. Use the 'Required' policy to instead block and
78 # return an error when the field path does not exist.
79 fromFieldPath: Required
80
81 # You can patch entire objects or arrays from one resource to another.
82 # By default the 'to' object or array will be overwritten, not merged.
83 # Use the 'mergeOptions' field to override this behaviour. Note that
84 # these fields accidentally leak Go terminology - 'slice' means 'array'.
85 # 'map' means 'map' in YAML or 'object' in JSON.
86 mergeOptions:
87 appendSlice: true
88 keepMapValues: true
89
90 # You can include connection details to propagate from this CloudSQLInstance
91 # up to the XPostgreSQLInstance XR (and then on to the PostgreSQLInstance
92 # claim). Remember that your XRD must declare which connection secret keys
93 # it supports.
94 connectionDetails:
95 - name: hostname
96 fromConnectionSecretKey: hostname
97
98 # By default an XR's 'Ready' status condition will become True when the
99 # 'Ready' status conditions of all of its composed resources become true.
100 # You can optionally specify custom readiness checks to override this.
101 readinessChecks:
102 - type: None
103
104
105 # If you find yourself repeating patches a lot you can group them as a named
106 # 'patch set' then use a PatchSet type patch to reference them.
107 patchSets:
108 - name: metadata
109 patches:
110 - type: FromCompositeFieldPath
111 # When both field paths are the same you can omit the 'toFieldPath' and it
112 # will default to the 'fromFieldPath'.
113 fromFieldPath: metadata.labels[some-important-label]
Pause Annotation
There is an annotation named crossplane.io/paused
that you can use on
Composite Resources and Composite Resource Claims to temporarily pause
reconciliations of their respective controllers on them. An example
for a Composite Resource Claim is as follows:
1apiVersion: test.com/v1alpha1
2kind: MyResource
3metadata:
4 annotations:
5 crossplane.io/paused: "true"
6 namespace: upbound-system
7 name: my-resource
8spec:
9 parameters:
10 tagValue: demo-test
11 compositionRef:
12 name: example
where MyResource
is a Composite Resource Claim kind.
When a Composite Resource or a Claim has the crossplane.io/paused
annotation
with its value set to true
, the Composite Resource controller or the Claim
controller pauses reconciliations on the resource until
the annotation is removed or its value set to something other than true
.
Before temporarily pausing reconciliations, an event with the type Synced
,
the status False
, and the reason ReconcilePaused
is emitted
on the resource.
Please also note that annotations on a Composite Resource Claim are propagated
to the associated Composite Resource but when the
crossplane.io/paused: "true"
annotation is added to a Claim, because
reconciliations on the Claim are now paused, this newly added annotation
will not be propagated. However, whenever the annotation’s value is set to a
non-true
value, reconciliations on the Claim will now resume, and thus the
annotation will now be propagated to the associated Composite Resource
with a non-true
value. An implication of the described behavior is that
pausing reconciliations on the Claim will not inherently pause reconciliations
on the associated Composite Resource.
Patch Types
You can use the following types of patch in a Composition
:
FromCompositeFieldPath
. The default if the type
is omitted. This type
patches from a field within the XR to a field within the composed resource. It’s
commonly used to expose a composed resource spec field as an XR spec field.
1# Patch from the XR's spec.parameters.size field to the composed resource's
2# spec.forProvider.settings.tier field.
3- type: FromCompositeFieldPath
4 fromFieldPath: spec.parameters.size
5 toFieldPath: spec.forProvider.settings.tier
ToCompositeFieldPath
. The inverse of FromCompositeFieldPath
. This type
patches from a field within the composed resource to a field within the XR. It’s
commonly used to derive an XR status field from a composed resource status
field.
1# Patch from the composed resource's status.atProvider.zone field to the XR's
2# status.zone field.
3- type: ToCompositeFieldPath
4 fromFieldPath: status.atProvider.zone
5 toFieldPath: status.zone
FromCompositeFieldPath
and ToCompositeFieldPath
patches can also take a wildcarded
field path in the toFieldPath
parameter and patch each array element in the toFieldPath
with the singular value provided in the fromFieldPath
.
1# Patch from the XR's spec.parameters.allowedIPs to the CIDRBlock elements
2# inside the array spec.forProvider.firewallRules on the composed resource.
3resources:
4- name: exampleFirewall
5 base:
6 apiVersion: firewall.example.crossplane.io/v1beta1
7 kind: Firewall
8 spec:
9 forProvider:
10 firewallRules:
11 - Action: "Allow"
12 Destination: "example1"
13 CIDRBlock: ""
14 - Action: "Allow"
15 Destination: "example2"
16 CIDRBlock: ""
17- type: FromCompositeFieldPath
18 fromFieldPath: spec.parameters.allowedIP
19 toFieldPath: spec.forProvider.firewallRules[*].CIDRBlock
FromEnvironmentFieldPath
. This type patches from a field within the in-memory
environment to a field within the composed resource. It’s commonly used to
expose a composed resource spec field as an XR spec field.
Note that EnvironmentConfigs are an alpha feature and need to be enabled with
the --enable-environment-configs
flag on startup.
1# Patch from the environment's tier.name field to the composed resource's
2# spec.forProvider.settings.tier field.
3- type: FromEnvironmentFieldPath
4 fromFieldPath: tier.name
5 toFieldPath: spec.forProvider.settings.tier
ToEnvironmentFieldPath
. This type patches from a composed field to the
in-memory environment. Note that, unlike ToCompositeFieldPath
patches, this
is executed before the composed resource is applied on the cluster which means
that the status
is not available.
Note that EnvironmentConfigs are an alpha feature and need to be enabled with
the --enable-environment-configs
flag on startup.
1# Patch from the environment's tier.name field to the composed resource's
2# spec.forProvider.settings.tier field.
3- type: ToEnvironmentFieldPath
4 fromFieldPath: spec.forProvider.settings.tier
5 toFieldPath: tier.name
Note that the field to be patched requires some initial value to be set.
CombineFromComposite
. Combines multiple fields from the XR to produce one
composed resource field.
1# Patch from the XR's spec.parameters.location field and the
2# metadata.labels[crossplane.io/claim-name] label to the composed
3# resource's spec.forProvider.administratorLogin field.
4- type: CombineFromComposite
5 combine:
6 # The patch will only be applied when all variables have non-zero values.
7 variables:
8 - fromFieldPath: spec.parameters.location
9 - fromFieldPath: metadata.labels[crossplane.io/claim-name]
10 strategy: string
11 string:
12 fmt: "%s-%s"
13 toFieldPath: spec.forProvider.administratorLogin
14 # By default Crossplane will skip the patch until all of the variables to be
15 # combined have values. Set the fromFieldPath policy to 'Required' to instead
16 # abort composition and return an error if a variable has no value.
17 policy:
18 fromFieldPath: Required
CombineFromEnvironment
. Combines multiple fields from the in-memory
environment to produce one composed resource field.
Note that EnvironmentConfigs are an alpha feature and need to be enabled with
the --enable-environment-configs
flag on startup.
1# Patch from the environments's location field and region to the composed
2# resource's spec.forProvider.administratorLogin field.
3- type: CombineFromEnvironment
4 combine:
5 # The patch will only be applied when all variables have non-zero values.
6 variables:
7 - fromFieldPath: location
8 - fromFieldPath: region
9 strategy: string
10 string:
11 fmt: "%s-%s"
12 toFieldPath: spec.forProvider.administratorLogin
At the time of writing only the string
combine strategy is supported. It uses
Go string formatting to combine values, so if the XR’s location was
us-west
and its claim name was db
the composed resource’s administratorLogin
would be set to us-west-db
.
CombineToComposite
is the inverse of CombineFromComposite
.
1# Patch from the composed resource's spec.parameters.administratorLogin and
2# status.atProvider.fullyQualifiedDomainName fields back to the XR's
3# status.adminDSN field.
4- type: CombineToComposite
5 combine:
6 variables:
7 - fromFieldPath: spec.parameters.administratorLogin
8 - fromFieldPath: status.atProvider.fullyQualifiedDomainName
9 strategy: string
10 # Here, our administratorLogin parameter and fullyQualifiedDomainName
11 # status are formatted to a single output string representing a DSN.
12 string:
13 fmt: "mysql://%s@%s:3306/my-database-name"
14 toFieldPath: status.adminDSN
CombineToEnvironment
is the inverse of CombineFromEnvironment
.
Note that EnvironmentConfigs are an alpha feature and need to be enabled with
the --enable-environment-configs
flag on startup.
1# Patch from the composed resource's spec.parameters.administratorLogin and
2# spec.forProvider.domainName fields back to the environment's adminDSN field.
3- type: CombineToEnvironment
4 combine:
5 variables:
6 - fromFieldPath: spec.parameters.administratorLogin
7 - fromFieldPath: spec.forProvider.domainName
8 strategy: string
9 # Here, our administratorLogin parameter and fullyQualifiedDomainName
10 # status are formatted to a single output string representing a DSN.
11 string:
12 fmt: "mysql://%s@%s:3306/my-database-name"
13 toFieldPath: adminDSN
PatchSet
. References a named set of patches defined in the spec.patchSets
array of a Composition
.
1# This is equivalent to specifying all of the patches included in the 'metadata'
2# PatchSet.
3- type: PatchSet
4 patchSetName: metadata
The patchSets
array may not contain patches of type: PatchSet
. The
transforms
and patchPolicy
fields are ignored by type: PatchSet
.
Transform Types
You can use the following types of transform on a value being patched:
map
. Transforms values using a map.
1# If the value of the 'from' field is 'us-west', the value of the 'to' field
2# will be set to 'West US'.
3- type: map
4 map:
5 us-west: West US
6 us-east: East US
7 au-east: Australia East
match
. A more complex version of map
that can match different kinds of
patterns. It should be used if more advanced pattern matchings than a simple
string equality check are required.
The result of the first matching pattern is used as the output of this
transform.
If no pattern matches, you can either fallback to a given fallbackValue
or
fallback to the input value by setting the fallbackTo
field to Input
.
1# In the example below, if the value in the 'from' field is 'us-west', the
2# value in the 'to' field will be set to 'West US'.
3# If the value in the 'from' field is 'eu-west', the value in the 'to' field
4# will be set to 'Unknown' because no pattern matches.
5- type: match
6 match:
7 patterns:
8 - type: literal # Not needed. This is the default.
9 literal: us-west
10 result: West US
11 - type: regexp
12 regexp: '^af-.*'
13 result: Somewhere in Africa
14 fallbackTo: Value # Not needed. This is the default.
15 fallbackValue: Unknown
16
17# If fallbackTo is set to Input, the output will be the input value if no
18# pattern matches.
19# In the example below, if the value in the 'from' field is 'us-west', the
20# value in the 'to' field will be set to 'West US'.
21# If the value in the 'from' field is 'eu-west', the value in the 'to' field
22# will be set to 'eu-west' because no pattern matches.
23- type: match
24 match:
25 patterns:
26 - type: literal
27 literal: us-west
28 result: West US
29 - type: regexp
30 regexp: '^af-.*'
31 result: Somewhere in Africa
32 fallbackTo: Input
math
. Transforms values using math. The input value must be an integer.
- math transform type
Multiply
, multiplies the input by the given value. - math transform type
ClampMin
, sets a minimum value for the output. - math transform type
ClampMax
, sets a maximum value for the output.
1# If you omit the field type, by default type is set to `Multiply`
2# If the value of the 'from' field is 2, the value of the 'to' field will be set
3# to 4.
4- type: math
5 math:
6 multiply: 2
7
8# This is the same as above
9# If the value of the 'from' field is 2, the value of the 'to' field will be set
10# to 4.
11- type: math
12 math:
13 type: Multiply
14 multiply: 2
15
16# If the value of the 'from' field is 3, the value of the 'to' field will
17# be set to 4.
18- type: math
19 math:
20 type: ClampMin
21 clampMin: 4
22
23# If the value of the 'from' field is 3, the value of the 'to' field will
24# be set to 2.
25- type: math
26 math:
27 type: ClampMax
28 clampMax: 2
string
. Transforms string values.
- string transform type
Format
, Currently only Go style fmt is supported. Go stylefmt
is supported. - string transform type
Convert
, accepts one ofToUpper
,ToLower
,ToBase64
,FromBase64
,ToJson
,ToSha1
,ToSha256
,ToSha512
. - string transform type
TrimPrefix
, accepts a string to be trimmed from the beginning of the input. - string transform type
TrimSuffix
, accepts a string to be trimmed from the end of the input. - string transform type
Regexp
, accepts a string for regexp to be applied to.
1# If you omit the field type, by default type is set to `Format`
2# If the value of the 'from' field is 'hello', the value of the 'to' field will
3# be set to 'hello-world'.
4- type: string
5 string:
6 fmt: "%s-world"
7
8# This is the same as above
9# the value of the 'to' field will be set to 'hello-world'.
10- type: string
11 string:
12 type: Format
13 fmt: "%s-world"
14
15# If the value of the 'from' field is 'hello', the value of the 'to' field will
16# be set to 'HELLO'.
17- type: string
18 string:
19 type: Convert
20 convert: ToUpper
21
22# If the value of the 'from' field is 'Hello', the value of the 'to' field will
23# be set to 'hello'.
24- type: string
25 string:
26 type: Convert
27 convert: ToLower
28
29# If the value of the 'from' field is 'Hello', the value of the 'to' field will
30# be set to 'SGVsbG8='.
31- type: string
32 string:
33 type: Convert
34 convert: ToBase64
35
36# If the value of the 'from' field is 'SGVsbG8=', the value of the 'to' field will
37# be set to 'Hello'.
38- type: string
39 string:
40 type: Convert
41 convert: FromBase64
42
43# If the value of the 'from' field is not nil, the value of the 'to' field will be
44# set to raw JSON representation of the 'from' field.
45- type: string
46 string:
47 type: Convert
48 convert: ToJson
49
50# The output will be the hash of the JSON representation of the 'from' field.
51- type: string
52 string:
53 type: Convert
54 convert: ToSha1 # alternatives: 'ToSha256' or 'ToSha512'
55
56# If the value of the 'from' field is https://crossplane.io, the value of the 'to' field will
57# be set to crossplane.io
58- type: string
59 string:
60 type: TrimPrefix
61 trim: 'https://'
62
63# If the value of the 'from' field is my-string-test, the value of the 'to' field will
64# be set to my-string
65- type: string
66 string:
67 type: TrimSuffix
68 trim: '-test'
69
70# If the value of the 'from' field is 'arn:aws:iam::42:example, the value of the
71# 'to' field will be set to "42". Note that the 'to' field is always a string.
72- type: string
73 string:
74 type: Regexp
75 regexp:
76 match: 'arn:aws:iam::(\d+):.*'
77 group: 1 # Optional capture group. Omit to match the entire regexp.
convert
. Transforms values of one type to another, for example from a string
to an integer. The following values are supported by the from
and to
fields:
string
bool
int
int64
float64
The strings 1, t, T, TRUE, true, and True are considered ’true’, while 0, f, F, FALSE, false, and False are considered ‘false’. The integer 1 and float 1.0 are considered true, while all other values are considered false. Similarly, boolean true converts to integer 1 and float 1.0, while false converts to 0 and 0.0.
1# If the value to be converted is "1" (a string), the value of the 'toType'
2# field will be set to 1 (an integer).
3- type: convert
4 convert:
5 toType: int
Converting string
to float64
additionally supports parsing string in
K8s quantity format,
such as 1000m
or 500 Mi
:
Connection Details
Connection details secret of XR is an aggregated sum of the connection details
of the composed resources. It’s recommended that the author of XRD specify
exactly which keys will be allowed in the XR connection secret by listing them
in spec.connectionSecretKeys
so that consumers of claims and XRs can see what
they can expect in the connection details secret.
If spec.connectionSecretKeys
is empty, then all keys of the aggregated connection
details secret will be propagated.
You can derive the following types of connection details from a composed resource to be aggregated:
FromConnectionSecretKey
. Derives an XR connection detail from a connection
secret key of a composed resource.
1# Derive the XR's 'user' connection detail from the 'username' key of the
2# composed resource's connection secret.
3- type: FromConnectionSecretKey
4 name: user
5 fromConnectionSecretKey: username
FromFieldPath
. Derives an XR connection detail from a field path within the
composed resource.
1# Derive the XR's 'user' connection detail from the 'adminUser' status field of
2# the composed resource.
3- type: FromFieldPath
4 name: user
5 fromFieldPath: status.atProvider.adminUser
FromValue
. Derives an XR connection detail from a fixed value.
1# Always sets the XR's 'user' connection detail to 'admin'.
2- type: FromValue
3 name: user
4 value: admin
Readiness Checks
Crossplane can use the following types of readiness check to determine whether a composed resource is ready (and therefore whether the XR and claim should be considered ready). Specify multiple readiness checks if multiple conditions must be met for a composed resource to be considered ready.
Note that if you don’t specify any readiness checks Crossplane will consider the composed resource to be ready when its ‘Ready’ status condition becomes ‘True’.
MatchString
. Considers the composed resource to be ready when the value of a
field within that resource matches a specified string.
1# The composed resource will be considered ready when the 'state' status field
2# matches the string 'Online'.
3- type: MatchString
4 fieldPath: status.atProvider.state
5 matchString: "Online"
MatchInteger
. Considers the composed resource to be ready when the value of a
field within that resource matches a specified integer.
1# The composed resource will be considered ready when the 'state' status field
2# matches the integer 4.
3- type: MatchInteger
4 fieldPath: status.atProvider.state
5 matchInteger: 4
NonEmpty
. Considers the composed resource to be ready when a field exists in
the composed resource. The name of this check can be a little confusing in that
a field that exists with a zero value (e.g. an empty string or zero integer) is
not considered to be ’empty’, and thus will pass the readiness check.
1# The composed resource will be considered ready if and when 'online' status
2# field exists.
3- type: NonEmpty
4 fieldPath: status.atProvider.online
None
. Considers the composed resource to be ready as soon as it exists.
Composition validation
Crossplane uses a Validating Webhook
to inform users of any potential
errors in a Composition
. By default webhooks only perform
logical checks
. logical checks
enforce requirements that
aren’t explicitly defined in the schema but Crossplane assumes to hold at runtime.
Experimental validation with schemas
Enable experimental schema-aware validation in Crossplane
through the --enable-composition-webhook-schema-validation
feature flag. This
enables Composition validation against available schemas in the cluster.
For example, ensuring that fieldPaths
are valid and source and destination
types match taking into account provided transforms too.
The crossplane.io/composition-validation-mode
annotation on the Composition
allows setting one of two modes for schema validation:
loose
(default): Validates Compositions against required schemas. If a required schema is missing, schema validation stops, emits a warning and falls back tological checks
only.strict
: Validates Compositions against required schemas, and rejects them when finding errors. Rejects any Compositions missing required schemas.
See the Composition Validating Webhook design document for more information about future development around schema-aware validation.
Disabling webhooks
Crossplane enables webhooks by default. Turn off webhooks by
webhooks.enabled
to false
in the provided Helm Chart.
Missing Functionality
You might find while reading through this reference that Crossplane is missing
some functionality you need to compose resources. If that’s the case, please
raise an issue with as much detail about your use case as possible. Please
understand that the Crossplane maintainers are growing the feature set of the
Composition
type conservatively. We highly value the input of our users and
community, but we also feel it’s critical to avoid bloat and complexity. We
therefore wish to carefully consider each new addition. We feel some features
may be better suited for a real, expressive programming language and intend to
build an alternative to the Composition
type as it’s documented here per
this proposal.
Tips, Tricks, and Troubleshooting
In this section we’ll cover some common tips, tricks, and troubleshooting steps for working with Composite Resources. If you’re trying to track down why your Composite Resources aren’t working the [Troubleshooting][trouble-ref] page also has some useful information.
Troubleshooting Claims and XRs
Crossplane relies heavily on status conditions and events for troubleshooting.
You can see both using kubectl describe
- for example:
1# Describe the PostgreSQLInstance claim named my-db
2kubectl describe postgresqlinstance.database.example.org my-db
Per Kubernetes convention, Crossplane keeps errors close to the place they
happen. This means that if your claim is not becoming ready due to an issue with
your Composition
or with a composed resource you’ll need to “follow the
references” to find out why. Your claim will only tell you that the XR is not
yet ready.
To follow the references:
- Find your XR by running
kubectl describe
on your claim and looking for its “Resource Ref” (akaspec.resourceRef
). - Run
kubectl describe
on your XR. This is where you’ll find out about issues with theComposition
you’re using, if any. - If there are no issues but your XR doesn’t seem to be becoming ready, take a
look for the “Resource Refs” (or
spec.resourceRefs
) to find your composed resources. - Run
kubectl describe
on each referenced composed resource to determine whether it is ready and what issues, if any, it is encountering.
Composite Resource Connection Secrets
Claim and Composite Resource connection secrets are often derived from the connection secrets of the managed resources they compose. This is a common source of confusion because several things need to align for it to work:
- The claim must specify the secret where the aggregated connection details
should be written
- This is the
spec.writeConnectionSecretToRef
field in a claim - If creating a composite resource directly (without a claim) then this same field must be set on your composite resource instead
- This is the
- The composite resource definition must state which connection details to
aggregate from its children to publish to the claim
- This is the
spec.connectionSecretKeys
field in aCompositeResourceDefinition
- This is the
- The composition must define where to write its aggregated connection
details
- This is the
spec.writeConnectionSecretsToNamespace
field in theComposition
- This is the
- Each child composed resource must define the connection details it
publishes and where to write them
- These are the
connectionDetails
andbase.spec.writeConnectionSecretToRef
fields of the composed resources
- These are the
Finally, you can’t currently edit a XRD’s supported connection details. The
XRD’s spec.connectionSecretKeys
is effectively immutable. This may change in
future per this issue
Claiming an Existing Composite Resource
Most people create Composite Resources using a claim, but you can actually claim an existing Composite Resource as long as its a type of XR that offers a claim and no one else has already claimed it. To do so:
- Set the
spec.resourceRef
of your claim to reference the existing XR. - Make sure the rest of your claim’s spec fields match the XR’s.
If your claim’s spec fields don’t match the XR’s Crossplane will still claim it but will then try to update the XR’s spec fields to match the claim’s.
Influencing External Names
The crossplane.io/external-name
annotation has special meaning to Crossplane
managed resources - it specifies the name (or identifier) of the resource in the
external system, for example the actual name of a CloudSQLInstance
in the GCP
API. Some managed resources don’t let you specify an external name - in those
cases Crossplane will set it for you to whatever the external system requires.
If you add the crossplane.io/external-name
annotation to a claim Crossplane
will automatically propagate it when it creates an XR. It’s good practice to
have your Composition
further propagate the annotation to one or more composed
resources, but it’s not required.
Mixing and Matching Providers
Crossplane has providers for many things in addition to the big clouds. Take a
look at the Upbound Marketplace to find many of them.
Keep in mind that you can mix and match managed resources from different
providers within a Composition
to create Composite Resources. For example you
might use provider-aws and provider-sql to create an XR that provisions an
RDSInstance
then creates an SQL Database
and User
, or provider-gcp and
provider-helm to create a GKECluster
and deploy a Helm Chart Release
to it.
Often when mixing and matching providers you’ll need to compose a
ProviderConfig
for one provider that loads credentials from the connection
secret of a managed resource from another provider. Sometimes you may need to
use an intermediary XR to mutate the connection details to suit your needs.
This example from provider-helm demonstrates using a GKE cluster
connection secret as Helm ProviderConfig
credentials.
Patching From One Composed Resource to Another or Itself
It’s not possible to patch directly from one composed resource to another -
i.e. from one entry in the spec.resources
array of a Composition
to another.
It is however possible to achieve this by using the XR as an intermediary. To do
so:
- Use a
ToCompositeFieldPath
patch to patch from your source composed resource to the XR. Typically you’ll want to patch to a status field or an annotation. - Use a
FromCompositeFieldPath
patch to patch from the ‘intermediary’ field you patched to in step 1 to a field on the destination composed resource.
Note that the source and the target composed resource can be the same.