This document is for an unreleased version of Crossplane.

This document applies to the Crossplane master branch and not to the latest release v2.4.

This guide discusses the use of “Composition Revisions” to make and roll back changes to a Crossplane Composition. It assumes familiarity with Crossplane and Compositions.

A Composition configures how Crossplane should reconcile a Composite Resource (XR). Put otherwise, when you create an XR the selected Composition determines what resources Crossplane creates in response. For example, you define a PlatformDB XR, which represents your organisation’s common database configuration of an Azure MySQL Server and some firewall rules. The Composition contains the ‘base’ configuration for the MySQL server and the firewall rules that the PlatformDB configuration extends.

A Composition associates with multiple XRs that use it. You might define a Composition named big-platform-db that’s used by ten different PlatformDB XRs. Often, in the interest of self-service, a different team manages the Composition than the actual PlatformDB XRs. For example a platform team member may write and maintain the Composition, while individual app teams create PlatformDB XRs that use said Composition.

Each Composition is mutable - you can update it as your organisation’s needs change. Updating a Composition without Composition Revisions can be a risky process. Crossplane constantly uses the Composition to ensure that your actual infrastructure - your MySQL Servers and firewall rules - match your desired state. If you have 10 PlatformDB XRs all using the big-platform-db Composition, all 10 of those XRs are instantly updated following any updates you make to the big-platform-db Composition.

Composition Revisions allow XRs to opt out of automatic updates. Instead you can update your XRs to use the latest Composition settings at your own pace. This enables you to canary changes to your infrastructure, or to roll back some XRs to previous Composition settings without rolling back all XRs.

Using composition revisions

When you enable Composition Revisions three things happen:

  1. Crossplane creates a CompositionRevision for each Composition update.
  2. Composite Resources gain a spec.crossplane.compositionRevisionRef field that specifies which CompositionRevision they use.
  3. Composite Resources gain a spec.crossplane.compositionUpdatePolicy field that specifies how Crossplane should update them to new Composition Revisions.

Each time you edit a Composition Crossplane automatically creates a CompositionRevision that represents that ‘revision’ of the Composition - that unique state. Crossplane allocates each revision an increasing revision number. This gives CompositionRevision consumers an idea about which revision is ’newest’.

You can discover which revisions exist using kubectl:

1# Find all revisions of the Composition named 'example'
2kubectl get compositionrevision -l crossplane.io/composition-name=example

This should produce output something like:

1NAME              REVISION   AGE
2example-18pdgs2   1          4m36s
3example-2bgdr31   2          73s
4example-xjrdmzz   3          61s

A Composition is a mutable resource that you can update as your needs change over time. Each CompositionRevision is an immutable snapshot of those needs at a particular time.

Crossplane behaves the same way by default whether you enable Composition Revisions or not. When you enable Composition Revisions all XRs default to the Automatic compositionUpdatePolicy. XRs support two update policies:

  • Automatic: Automatically use the latest CompositionRevision. (Default)
  • Manual: Require manual intervention to change CompositionRevision.

The below XR uses the Manual policy. When you use this policy the XR selects the latest CompositionRevision when it’s first created, but must manually update it when you wish it to use another CompositionRevision.

 1apiVersion: example.org/v1alpha1
 2kind: PlatformDB
 3metadata:
 4  namespace: default
 5  name: example
 6spec:
 7  storageGB: 20
 8  crossplane:
 9    # The Manual policy specifies that you don't want this XR to update to the
10    # latest CompositionRevision automatically.
11    compositionUpdatePolicy: Manual
12    compositionRef:
13      name: example

Crossplane sets an XR’s compositionRevisionRef automatically at creation time regardless of your chosen compositionUpdatePolicy. If you choose the Manual policy you must edit the compositionRevisionRef field when you want your XR to use a different CompositionRevision.

 1apiVersion: example.org/v1alpha1
 2kind: PlatformDB
 3metadata:
 4  namespace: default
 5  name: example
 6spec:
 7  storageGB: 20
 8  crossplane:
 9    compositionUpdatePolicy: Manual
10    compositionRef:
11      name: example
12    # Update the referenced CompositionRevision if and when you are ready.
13    compositionRevisionRef:
14      name: example-18pdg

Roll out function changes

Important
Referencing functions by OCI reference is an alpha feature. Enable it by setting the --enable-pipeline-oci-references feature flag.

A CompositionRevision only captures what’s in the Composition. A pipeline step that uses functionRef calls whatever version of that Function is active in the cluster. When you upgrade a Function every CompositionRevision that references it starts calling the new version, including revisions you’ve pinned XRs to with the Manual update policy.

To make the function version part of the revision, reference the function package directly with the function field instead of using functionRef:

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  name: example
 5spec:
 6  compositeTypeRef:
 7    apiVersion: example.org/v1alpha1
 8    kind: PlatformDB
 9  mode: Pipeline
10  pipeline:
11  - step: render-templates
12    function: xpkg.crossplane.io/crossplane-contrib/function-go-templating@sha256:2edec9149ed78cfc109fdda53fef847055e4789f1d5984ffc2f9ce2b54241abc
13    input:
14      apiVersion: gotemplating.fn.crossplane.io/v1beta1
15      kind: GoTemplate
16      source: Inline
17      # Removed for brevity

The function field must be a fully qualified OCI reference that includes the registry, the repository and a digest. A digest provides an immutable reference to a specific version of a package, so a CompositionRevision that references a function this way always runs the same function code.

Each pipeline step uses either function or functionRef, not both. A pipeline can contain a mix of steps using the two fields.

You don’t need to install a function before you reference it with function. When Crossplane creates a CompositionRevision, it creates a FunctionRevision for each function package the pipeline references. CompositionRevisions that reference the same digest share a FunctionRevision. Different digests of the same function run side by side, each with its own function pod.

The CompositionRevisions that reference a FunctionRevision own it. The FunctionRevision is garbage collected by Kubernetes when you delete the last CompositionRevision that references it.

Read the Functions documentation for more details on how Crossplane manages these functions.

Canary a function upgrade

Combine function with a compositionRevisionSelector to roll a new function version out to some XRs before others.

Start with a Composition that calls version v0.13.1 of function-go-templating and has a channel: stable label:

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  labels:
 5    channel: stable
 6  name: example
 7spec:
 8  compositeTypeRef:
 9    apiVersion: example.org/v1alpha1
10    kind: PlatformDB
11  mode: Pipeline
12  pipeline:
13  - step: render-templates
14    function: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.13.1@sha256:2edec9149ed78cfc109fdda53fef847055e4789f1d5984ffc2f9ce2b54241abc
15    input:
16      # Removed for brevity

Most of your XRs select the stable channel. XRs that should use the new version first select canary:

 1apiVersion: example.org/v1alpha1
 2kind: PlatformDB
 3metadata:
 4  namespace: default
 5  name: canary-db
 6spec:
 7  storageGB: 20
 8  crossplane:
 9    compositionRevisionSelector:
10      matchLabels:
11        channel: canary

To try function-go-templating v0.13.2, update the function reference and change the label to channel: canary in the same edit:

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  labels:
 5    channel: canary
 6  name: example
 7spec:
 8  compositeTypeRef:
 9    apiVersion: example.org/v1alpha1
10    kind: PlatformDB
11  mode: Pipeline
12  pipeline:
13  - step: render-templates
14    function: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.13.2@sha256:20198cb6ea5b4b8a48042343410ec9031e5ec43431d874364ecd3eac8e861290
15    input:
16      # Removed for brevity

Crossplane creates a new CompositionRevision and a new FunctionRevision for v0.13.2. Both versions of the function are now running:

1kubectl get functionrevisions -o="custom-columns=NAME:.metadata.name,PACKAGE:.spec.package,STATE:.spec.desiredState"
2NAME                                                     PACKAGE                                                                                                                                STATE
3crossplane-contrib-function-go-templating-91622b9260e8   xpkg.crossplane.io/crossplane-contrib/function-go-templating@sha256:20198cb6ea5b4b8a48042343410ec9031e5ec43431d874364ecd3eac8e861290   Active
4crossplane-contrib-function-go-templating-d66b8d13aa14   xpkg.crossplane.io/crossplane-contrib/function-go-templating@sha256:2edec9149ed78cfc109fdda53fef847055e4789f1d5984ffc2f9ce2b54241abc   Active

XRs that select canary use the new CompositionRevision and call v0.13.2 of the function, while XRs that select stable stay on the previous CompositionRevision that uses v0.13.1.

When you’re happy with v0.13.2, change the Composition label back to channel: stable. Crossplane creates another CompositionRevision with the same v0.13.2 reference and the stable label, and the rest of your XRs use it.

Tip
Use the Manual update policy for tighter control. Each XR stays on the CompositionRevision that last composed it, and the pinned function version, until you change its compositionRevisionRef.

Complete example

This tutorial discusses how CompositionRevisions work and how they manage Composite Resource (XR) updates. This starts with a Composition and CompositeResourceDefinition (XRD) that defines a MyVPC resource and continues with creating multiple XRs to observe different upgrade paths. Crossplane assigns different CompositionRevisions to composite resources each time you update the composition.

Preparation

Deploy composition and XRD examples

Apply the example Composition.

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  labels:
 5    channel: dev
 6  name: myvpcs.aws.example.upbound.io
 7spec:
 8  compositeTypeRef:
 9    apiVersion: aws.example.upbound.io/v1alpha1
10    kind: MyVPC
11  mode: Pipeline
12  pipeline:
13  - step: patch-and-transform
14    functionRef:
15      name: function-patch-and-transform
16    input:
17      apiVersion: pt.fn.crossplane.io/v1beta1
18      kind: Resources
19      resources:
20      - name: my-vpc
21        base:
22          apiVersion: ec2.aws.m.upbound.io/v1beta1
23          kind: VPC
24          spec:
25            forProvider:
26              region: us-west-1
27              cidrBlock: 192.168.0.0/16
28              enableDnsSupport: true
29              enableDnsHostnames: true

Apply the example XRD.

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: CompositeResourceDefinition
 3metadata:
 4  name: myvpcs.aws.example.upbound.io
 5spec:
 6  group: aws.example.upbound.io
 7  names:
 8    kind: MyVPC
 9    plural: myvpcs
10  versions:
11  - name: v1alpha1
12    served: true
13    referenceable: true
14    schema:
15      openAPIV3Schema:
16        type: object
17        properties:
18          spec:
19            type: object
20            properties:
21              id:
22                type: string
23                description: ID of this VPC that other objects will use to refer to it.
24            required:
25            - id

Verify that Crossplane created the Composition revision

1kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"

Expected Output:

1NAME                                    REVISION   CHANNEL
2myvpcs.aws.example.upbound.io-ad265bc   1          dev
Note
The label dev is automatically created from the Composition.

Create composite resources

This tutorial has four composite resources to cover different update policies and composition selection options. The default behavior is updating XRs to the latest revision of the Composition. You can change this by setting compositionUpdatePolicy: Manual in the XR. It’s also possible to select the latest revision with a specific label with compositionRevisionSelector.matchLabels together with compositionUpdatePolicy: Automatic.

Default update policy

Create an XR without a compositionUpdatePolicy defined. The update policy is Automatic by default:

1apiVersion: aws.example.upbound.io/v1alpha1
2kind: MyVPC
3metadata:
4  namespace: default
5  name: vpc-auto
6spec:
7  id: vpc-auto

Expected Output:

1myvpc.aws.example.upbound.io/vpc-auto created

Manual update policy

Create a Composite Resource with compositionUpdatePolicy: Manual and compositionRevisionRef.

 1apiVersion: aws.example.upbound.io/v1alpha1
 2kind: MyVPC
 3metadata:
 4  namespace: default
 5  name: vpc-man
 6spec:
 7  id: vpc-man
 8  crossplane:
 9    compositionUpdatePolicy: Manual
10    compositionRevisionRef:
11      name: myvpcs.aws.example.upbound.io-ad265bc

Expected Output:

1myvpc.aws.example.upbound.io/vpc-man created

Using a selector

Create an XR with a compositionRevisionSelector of channel: dev:

 1apiVersion: aws.example.upbound.io/v1alpha1
 2kind:  MyVPC
 3metadata:
 4  namespace: default
 5  name: vpc-dev
 6spec:
 7  id: vpc-dev
 8  crossplane:
 9    compositionRevisionSelector:
10      matchLabels:
11        channel: dev

Expected Output:

1myvpc.aws.example.upbound.io/vpc-dev created

Create an XR with a compositionRevisionSelector of channel: staging:

 1apiVersion: aws.example.upbound.io/v1alpha1
 2kind: MyVPC
 3metadata:
 4  namespace: default
 5  name: vpc-staging
 6spec:
 7  id: vpc-staging
 8  crossplane:
 9    compositionRevisionSelector:
10      matchLabels:
11        channel: staging

Expected Output:

1myvpc.aws.example.upbound.io/vpc-staging created

Verify the Composite Resource with the label channel: staging doesn’t have a REVISION. All other XRs have a REVISION matching the created Composition Revision.

1kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.crossplane.compositionRevisionRef.name,POLICY:.spec.crossplane.compositionUpdatePolicy,MATCHLABEL:.spec.crossplane.compositionRevisionSelector.matchLabels"

Expected Output:

1NAME          SYNCED   REVISION                                POLICY      MATCHLABEL
2vpc-auto      True     myvpcs.aws.example.upbound.io-ad265bc   Automatic   <none>
3vpc-dev       True     myvpcs.aws.example.upbound.io-ad265bc   Automatic   map[channel:dev]
4vpc-man       True     myvpcs.aws.example.upbound.io-ad265bc   Manual      <none>
5vpc-staging   False    <none>                                  Automatic   map[channel:staging]
Note
The vpc-staging XR label doesn’t match any existing Composition Revisions.

Create new composition revisions

Crossplane creates a new CompositionRevision when you create or update a Composition. Label and annotation changes also trigger a new CompositionRevision.

Update the composition label

Update the Composition label to channel: staging:

1kubectl label composition myvpcs.aws.example.upbound.io channel=staging --overwrite

Expected Output:

1composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io labeled

Verify that Crossplane creates a new Composition revision:

1kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"

Expected Output:

1NAME                                    REVISION   CHANNEL
2myvpcs.aws.example.upbound.io-727b3c8   2          staging
3myvpcs.aws.example.upbound.io-ad265bc   1          dev

Verify that Crossplane assigns the Composite Resources vpc-auto and vpc-staging to Composite revision:2. XRs vpc-man and vpc-dev are still assigned to the original revision:1:

1kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.crossplane.compositionRevisionRef.name,POLICY:.spec.crossplane.compositionUpdatePolicy,MATCHLABEL:.spec.crossplane.compositionRevisionSelector.matchLabels"

Expected Output:

1NAME          SYNCED   REVISION                                POLICY      MATCHLABEL
2vpc-auto      True     myvpcs.aws.example.upbound.io-727b3c8   Automatic   <none>
3vpc-dev       True     myvpcs.aws.example.upbound.io-ad265bc   Automatic   map[channel:dev]
4vpc-man       True     myvpcs.aws.example.upbound.io-ad265bc   Manual      <none>
5vpc-staging   True     myvpcs.aws.example.upbound.io-727b3c8   Automatic   map[channel:staging]
Note
vpc-auto always use the latest Revision. vpc-staging now matches the label applied to Revision revision:2.

Update composition spec and label

Update the Composition to disable DNS support in the VPC and change the label from staging back to dev.

Apply the following changes to update the Composition spec and label:

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  labels:
 5    channel: dev
 6  name: myvpcs.aws.example.upbound.io
 7spec:
 8  compositeTypeRef:
 9    apiVersion: aws.example.upbound.io/v1alpha1
10    kind: MyVPC
11  mode: Pipeline
12  pipeline:
13  - step: patch-and-transform
14    functionRef:
15      name: function-patch-and-transform
16    input:
17      apiVersion: pt.fn.crossplane.io/v1beta1
18      kind: Resources
19      resources:
20      - name: my-vpc
21        base:
22          apiVersion: ec2.aws.m.upbound.io/v1beta1
23          kind: VPC
24          spec:
25            forProvider:
26              region: us-west-1
27              cidrBlock: 192.168.0.0/16
28              enableDnsSupport: false
29              enableDnsHostnames: true

Expected Output:

1composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io configured

Verify that Crossplane creates a new Composition revision:

1kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"

Expected Output:

1NAME                                    REVISION   CHANNEL
2myvpcs.aws.example.upbound.io-727b3c8   2          staging
3myvpcs.aws.example.upbound.io-ad265bc   1          dev
4myvpcs.aws.example.upbound.io-f81c553   3          dev
Note
Changing the label and the spec values simultaneously is critical for deploying new changes to the dev channel.

Verify Crossplane assigns the Composite Resources vpc-auto and vpc-dev to Composite revision:3. Crossplane assigns vpc-staging to revision:2, and still assigns vpc-man to the original revision:1:

1kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.crossplane.compositionRevisionRef.name,POLICY:.spec.crossplane.compositionUpdatePolicy,MATCHLABEL:.spec.crossplane.compositionRevisionSelector.matchLabels"

Expected Output:

1NAME          SYNCED   REVISION                                POLICY      MATCHLABEL
2vpc-auto      True     myvpcs.aws.example.upbound.io-f81c553   Automatic   <none>
3vpc-dev       True     myvpcs.aws.example.upbound.io-f81c553   Automatic   map[channel:dev]
4vpc-man       True     myvpcs.aws.example.upbound.io-ad265bc   Manual      <none>
5vpc-staging   True     myvpcs.aws.example.upbound.io-727b3c8   Automatic   map[channel:staging]
Note
vpc-dev matches the updated label applied to Revision revision:3. vpc-staging matches the label applied to Revision revision:2.