Connection Details Composition

This document is for an older version of Crossplane.

This document applies to Crossplane v2.0 and not to the latest release v2.1.

This guide shows how to expose connection details for composite resources (XRs). Because composite resources can compose multiple resources, the connection details they expose are often an aggregate of the connection details from their composed resources.

The recommended approach is to include a Kubernetes Secret resource in your Composition that aggregates the connection details from other resources and exposes them for the XR.

Note

Crossplane v1 included a feature that automatically created connection details for XRs.

To learn more about how to specify XR connection details in Crossplane v1, please see the v1 connection details docs page.

Example overview

This guide shows how composite resources can expose connection details by creating a UserAccessKey composite resource. This XR represents an AWS IAM user with multiple access keys.

When a user creates a UserAccessKey, Crossplane provisions an IAM User and two AccessKeys in AWS. Each AccessKey produces their own connection details like a username and password. The UserAccessKey also composes a Secret resource that exposes the aggregated connection details of its composed resources, allowing users and applications to consume them.

An example UserAccessKey XR looks like this:

1apiVersion: example.org/v1alpha1
2kind: UserAccessKey
3metadata:
4  namespace: default
5  name: my-keys

Behind the scenes, Crossplane:

  1. Creates an AWS IAM User and two AccessKeys (the composed resources)
  2. Collects connection details from both AccessKeys
  3. Exposes them as the UserAccessKey’s connection details in a Secret

The composite resource’s connection details Secret looks like this:

 1apiVersion: v1
 2kind: Secret
 3metadata:
 4  namespace: default
 5  name: my-keys-connection-details
 6data:
 7  user-0: <base64-encoded-username>
 8  password-0: <base64-encoded-password>
 9  user-1: <base64-encoded-username>
10  password-1: <base64-encoded-password>

Users and applications can consume the UserAccessKey connection details by reading this Secret.

Tip

The pattern in this guide applies to any composite resource that needs to expose connection details, for example:

  • Database connection strings and credentials
  • Cluster client certificate and key data
  • Application endpoints from services and ingress

Prerequisites

This guide requires:

Tip

To set up the AWS provider, follow the Get Started with Managed Resources guide, but use provider provider-aws-iam:v2.3.0 instead.

Complete the steps to install the provider and configure credentials, then return to this guide.

Build the composite resource

Follow these steps to create a composite resource that exposes connection details:

  1. Define the schema of the composite resource
  2. Install the composition function you want to use
  3. Configure how the composition exposes connection details

After you complete these steps you can use the composite resource.

Define the schema

A CompositeResourceDefinition (XRD) defines composite resources.

For this example, create an XRD for the UserAccessKey composite resource:

 1apiVersion: apiextensions.crossplane.io/v2
 2kind: CompositeResourceDefinition
 3metadata:
 4  name: useraccesskeys.example.org
 5spec:
 6  group: example.org
 7  names:
 8    kind: UserAccessKey
 9    plural: useraccesskeys
10  scope: Namespaced
11  versions:
12  - name: v1alpha1
13    served: true
14    referenceable: true
15    schema:
16      openAPIV3Schema:
17        type: object
18        properties:
19          spec:
20            type: object
21            properties:
22              writeConnectionSecretToRef:
23                type: object
24                properties:
25                  name:
26                    type: string
Tip

This XRD schema defines a .spec.writeConnectionSecretToRef.name field that allows the user to optionally set the name for the XR connection details secret.

For a Cluster scoped XRD, a .spec.writeConnectionSecretToRef.namespace field could also be added to allow the user to specify the namespace of the secret too.

Save the XRD as xrd.yaml and apply it:

1kubectl apply -f xrd.yaml

The Kubernetes API is now serving requests for the UserAccessKey composite resource.

Install the function

Composition functions provide general features to help you compose resources and expose connection details. This guide shows how to compose connection details with multiple functions. Pick the language you want to use from the tabs below.

Templated YAML is a good choice if you’re used to writing Helm charts.

Create this composition function to install templated YAML support:

1apiVersion: pkg.crossplane.io/v1
2kind: Function
3metadata:
4  name: function-go-templating
5spec:
6  package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2

Save the function as fn.yaml and apply it:

1kubectl apply -f fn.yaml

Check that Crossplane installed the function:

1kubectl get -f fn.yaml
2NAME                     INSTALLED   HEALTHY   PACKAGE                                                                AGE
3function-go-templating   True        True      xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2   15s

Create this composition function to install Python support:

1apiVersion: pkg.crossplane.io/v1
2kind: Function
3metadata:
4  name: function-python
5spec:
6  package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0

Save the function as fn.yaml and apply it:

1kubectl apply -f fn.yaml

Check that Crossplane installed the function:

1kubectl get -f fn.yaml
2NAME                                 INSTALLED   HEALTHY   PACKAGE                                                        AGE
3function-python                      True        True      xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0   12s

Create this composition function to install KCL support:

1apiVersion: pkg.crossplane.io/v1
2kind: Function
3metadata:
4  name: function-kcl
5spec:
6  package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6

Save the function as fn.yaml and apply it:

1kubectl apply -f fn.yaml

Check that Crossplane installed the function:

1kubectl get -f fn.yaml
2NAME                              INSTALLED   HEALTHY   PACKAGE                                                      AGE
3function-kcl                      True        True      xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6   6s

This guide also uses function-auto-ready. This function automatically marks composed resources as ready when they’re healthy:

1apiVersion: pkg.crossplane.io/v1
2kind: Function
3metadata:
4  name: function-auto-ready
5spec:
6  package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.0

Save this as fn-auto-ready.yaml and apply it:

1kubectl apply -f fn-auto-ready.yaml

Configure the composition

A Composition tells Crossplane how to compose resources for a composite resource. This guide also includes a composed Secret resource to expose the composite resource’s connection details.

The general pattern is:

  1. Composed resources write their connection details to individual secrets
  2. The Composition reads those connection details when the function runs
  3. The Composition creates a composed Secret representing the aggregated connection details for the XR
Tip

The composite resource’s connection details secret can contain any data you want and you can transform it as needed.

You’re not limited to connection details from managed resources - you can include data from any composed resource, including arbitrary Kubernetes resources like ConfigMaps or Services.

Create a Composition that exposes connection details for the UserAccessKey composite resource.

In this example, the Composition creates two AccessKey managed resources and exposes their credentials as the composite resource’s connection details Secret:

 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  name: useraccesskeys-go-templating
 5spec:
 6  compositeTypeRef:
 7    apiVersion: example.org/v1alpha1
 8    kind: UserAccessKey
 9  mode: Pipeline
10  pipeline:
11  - step: render-templates
12    functionRef:
13      name: function-go-templating
14    input:
15      apiVersion: gotemplating.fn.crossplane.io/v1beta1
16      kind: GoTemplate
17      source: Inline
18      inline:
19        template: |
20          ---
21          apiVersion: iam.aws.m.upbound.io/v1beta1
22          kind: User
23          metadata:
24            annotations:
25              {{ setResourceNameAnnotation "user" }}
26          spec:
27            forProvider: {}
28          ---
29          apiVersion: iam.aws.m.upbound.io/v1beta1
30          kind: AccessKey
31          metadata:
32            annotations:
33              {{ setResourceNameAnnotation "accesskey-0" }}
34          spec:
35            forProvider:
36              userSelector:
37                matchControllerRef: true
38            writeConnectionSecretToRef:
39              name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-0
40          ---
41          apiVersion: iam.aws.m.upbound.io/v1beta1
42          kind: AccessKey
43          metadata:
44            annotations:
45              {{ setResourceNameAnnotation "accesskey-1" }}
46          spec:
47            forProvider:
48              userSelector:
49                matchControllerRef: true
50            writeConnectionSecretToRef:
51              name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-1
52          ---
53          apiVersion: v1
54          kind: Secret
55          metadata:
56            name: {{ dig "spec" "writeConnectionSecretToRef" "name" "" $.observed.composite.resource}}
57            annotations:
58              {{ setResourceNameAnnotation "connection-secret" }}
59          {{ if eq $.observed.resources nil }}
60          data: {}
61          {{ else }}
62          data:
63            user-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.username }}
64            user-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.username }}
65            password-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.password }}
66            password-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.password }}
67          {{ end }}
68  - step: ready
69    functionRef:
70      name: function-auto-ready

How this Composition exposes connection details:

  • Each composed AccessKey has writeConnectionSecretToRef set. This tells each AccessKey to write its credentials to an individual Secret.
  • The Composition creates an explicit Secret resource that represents the composite resource’s connection details.
  • The name of the Secret is set using the dig function to read the XR’s .spec.writeConnectionSecretToRef.name field if it exists.
  • Crossplane observes the connection details from each AccessKey and makes them available to the composition when the function runs.
  • The Secret reads connection details via $.observed.resources from the observed composed resources.
  • The {{ if eq $.observed.resources nil }} check handles the initial phase when composed resources are still being created.
  • In function-go-templating, connection details are already base64-encoded, so you use them directly in the Secret’s data field.
  1apiVersion: apiextensions.crossplane.io/v1
  2kind: Composition
  3metadata:
  4  name: useraccesskeys-python
  5spec:
  6  compositeTypeRef:
  7    apiVersion: example.org/v1alpha1
  8    kind: UserAccessKey
  9  mode: Pipeline
 10  pipeline:
 11  - step: render-python
 12    functionRef:
 13      name: function-python
 14    input:
 15      apiVersion: python.fn.crossplane.io/v1beta1
 16      kind: Script
 17      script: |
 18        def compose(req, rsp):
 19            # Get observed composite resource
 20            oxr = req.observed.composite.resource
 21            oxr_name = oxr["metadata"]["name"]
 22
 23            # IAM User
 24            rsp.desired.resources["user"].resource.update({
 25                "apiVersion": "iam.aws.m.upbound.io/v1beta1",
 26                "kind": "User",
 27                "spec": {
 28                    "forProvider": {}
 29                }
 30            })
 31
 32            # Access Key 0
 33            rsp.desired.resources["accesskey-0"].resource.update({
 34                "apiVersion": "iam.aws.m.upbound.io/v1beta1",
 35                "kind": "AccessKey",
 36                "spec": {
 37                    "forProvider": {
 38                        "userSelector": {
 39                            "matchControllerRef": True
 40                        }
 41                    },
 42                    "writeConnectionSecretToRef": {
 43                        "name": f"{oxr_name}-accesskey-secret-0"
 44                    }
 45                }
 46            })
 47
 48            # Access Key 1
 49            rsp.desired.resources["accesskey-1"].resource.update({
 50                "apiVersion": "iam.aws.m.upbound.io/v1beta1",
 51                "kind": "AccessKey",
 52                "spec": {
 53                    "forProvider": {
 54                        "userSelector": {
 55                            "matchControllerRef": True
 56                        }
 57                    },
 58                    "writeConnectionSecretToRef": {
 59                        "name": f"{oxr_name}-accesskey-secret-1"
 60                    }
 61                }
 62            })
 63
 64            # Secret representing the composite resource's connection details
 65            secret_resource = {
 66                "apiVersion": "v1",
 67                "kind": "Secret",
 68                "metadata": {}
 69            }
 70
 71            # If a secret name was provided then use it
 72            secret_name = ""
 73            if "writeConnectionSecretToRef" in oxr["spec"] and "name" in oxr["spec"]["writeConnectionSecretToRef"]:
 74              secret_name = oxr["spec"]["writeConnectionSecretToRef"]["name"]
 75
 76            secret_resource["metadata"]["name"] = secret_name
 77
 78            # Only add data if we have connection details to populate
 79            data = {}
 80            if "accesskey-0" in req.observed.resources:
 81                accesskey0_conn = req.observed.resources["accesskey-0"].connection_details
 82                if "username" in accesskey0_conn:
 83                    data["user-0"] = accesskey0_conn["username"].decode("utf-8")
 84                if "password" in accesskey0_conn:
 85                    data["password-0"] = accesskey0_conn["password"].decode("utf-8")
 86
 87            if "accesskey-1" in req.observed.resources:
 88                accesskey1_conn = req.observed.resources["accesskey-1"].connection_details
 89                if "username" in accesskey1_conn:
 90                    data["user-1"] = accesskey1_conn["username"].decode("utf-8")
 91                if "password" in accesskey1_conn:
 92                    data["password-1"] = accesskey1_conn["password"].decode("utf-8")
 93
 94            if data:
 95                secret_resource["stringData"] = data
 96
 97            rsp.desired.resources["connection-secret"].resource.update(secret_resource)
 98  - step: ready
 99    functionRef:
100      name: function-auto-ready

How this Composition exposes connection details:

  • Each composed AccessKey has writeConnectionSecretToRef set. This tells each AccessKey to write its credentials to an individual Secret.
  • The Composition creates an explicit Secret resource that represents the composite resource’s connection details.
  • The secret_name is set only after checking that the XR’s .spec.writeConnectionSecretToRef.name field exists.
  • Crossplane observes the connection details from each AccessKey and makes them available to the composition when the function runs.
  • The Secret reads connection details via req.observed.resources[“accesskey-0”].connection_details from the observed composed resources.
  • The if “accesskey-0” in req.observed.resources check handles the initial phase when composed resources are still being created.
  • In function-python, connection details are plaintext bytes. To store them on the Secret, first convert them to strings with .decode(“utf-8”) and then save them using the secret’s stringData field.
 1apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  name: useraccesskeys-kcl
 5spec:
 6  compositeTypeRef:
 7    apiVersion: example.org/v1alpha1
 8    kind: UserAccessKey
 9  mode: Pipeline
10  pipeline:
11  - step: render-kcl
12    functionRef:
13      name: function-kcl
14    input:
15      apiVersion: krm.kcl.dev/v1alpha1
16      kind: KCLInput
17      spec:
18        source: |
19          oxr = option("params").oxr
20          ocds = option("params").ocds
21
22          user = {
23              apiVersion = "iam.aws.m.upbound.io/v1beta1"
24              kind = "User"
25              metadata.annotations = {
26                  "krm.kcl.dev/composition-resource-name" = "user"
27              }
28              spec.forProvider = {}
29          }
30
31          accesskey0 = {
32              apiVersion = "iam.aws.m.upbound.io/v1beta1"
33              kind = "AccessKey"
34              metadata.annotations = {
35                  "krm.kcl.dev/composition-resource-name" = "accesskey-0"
36              }
37              spec.forProvider.userSelector.matchControllerRef = True
38              spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-0"
39          }
40
41          accesskey1 = {
42              apiVersion = "iam.aws.m.upbound.io/v1beta1"
43              kind = "AccessKey"
44              metadata.annotations = {
45                  "krm.kcl.dev/composition-resource-name" = "accesskey-1"
46              }
47              spec.forProvider.userSelector.matchControllerRef = True
48              spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-1"
49          }
50
51          secret = {
52              apiVersion = "v1"
53              kind = "Secret"
54              metadata.name = oxr?.spec?.writeConnectionSecretToRef?.name or ""
55              metadata.annotations = {
56                  "krm.kcl.dev/composition-resource-name" = "connection-secret"
57              }
58              data = {
59                  "user-0" = ocds["accesskey-0"]?.ConnectionDetails?.username or ""
60                  "user-1" = ocds["accesskey-1"]?.ConnectionDetails?.username or ""
61                  "password-0" = ocds["accesskey-0"]?.ConnectionDetails?.password or ""
62                  "password-1" = ocds["accesskey-1"]?.ConnectionDetails?.password or ""
63              } if ocds else {}
64          }
65
66          items = [user, accesskey0, accesskey1, secret]
67  - step: ready
68    functionRef:
69      name: function-auto-ready

How this Composition exposes connection details:

  • Each composed AccessKey has writeConnectionSecretToRef set. This tells each AccessKey to write its credentials to an individual Secret.
  • The Composition creates an explicit Secret resource that represents the composite resource’s connection details.
  • The name of the Secret is set using ?. optional chaining operators to read the XR’s .spec.writeConnectionSecretToRef.name field if it exists.
  • Crossplane observes the connection details from each AccessKey and makes them available to the composition when the function runs.
  • The Secret reads connection details via ocds[“accesskey-0”]?.ConnectionDetails?.username from the observed composed resources, handling the case where connection details don’t exist yet.
  • The if ocds else {} handles the phase when composed resources are still being created.
  • In function-kcl, connection details are already base64-encoded, so you use them directly in the Secret’s data field.

Save the composition as composition.yaml and apply it:

1kubectl apply -f composition.yaml

Use the composite resource

The Composition now specifies how to compose connection details for the UserAccessKey composite resource.

Create a UserAccessKey to see it in action:

1apiVersion: example.org/v1alpha1
2kind: UserAccessKey
3metadata:
4  namespace: default
5  name: my-keys
6spec:
7  writeConnectionSecretToRef:
8    name: my-keys-connection-details

Save the composite resource as my-keys.yaml and apply it:

1kubectl apply -f my-keys.yaml

Check that the composite resource is ready:

1kubectl get -f my-keys.yaml
2NAME      SYNCED   READY   COMPOSITION                    AGE
3my-keys   True     True    useraccesskeys-go-templating   45s
Note
It may take a minute for AWS to provision the IAM resources. The composite resource becomes READY when all composed resources are healthy.

Verify the connection details

Composite resources expose their connection details through a Secret. Check that Crossplane created the Secret.

View all the composed resources (including the connection details Secret) together using the crossplane CLI.

Tip
See the Crossplane CLI docs to learn how to install and use the Crossplane CLI.
1crossplane beta trace useraccesskey.example.org/my-keys
2NAME                                             SYNCED   READY   STATUS
3UserAccessKey/my-keys (default)                  True     True    Available
4├─ AccessKey/my-keys-14c0578cad85 (default)      True     True    Available
5├─ AccessKey/my-keys-e420789d13a3 (default)      True     True    Available
6├─ User/my-keys-c63b530f8e68 (default)           True     True    Available
7└─ Secret/my-keys-connection-details (default)   -        -

The my-keys composite resource created an IAM User and two IAM AccessKeys, and a Secret was also created that contains the aggregated connection details for the composite resource.

Check the composite resource’s aggregated connection details Secret:

1kubectl get secret -n default -l crossplane.io/composite=my-keys
2NAME                   TYPE     DATA   AGE
3my-keys-586e2994bda1   Opaque   4      5m37s
Tip

The composite resource’s connection details Secret has a label crossplane.io/composite=my-keys for convenient lookup.

If you set .spec.writeConnectionSecretToRef.name on the XR, the Secret has that exact name.

Verify the composite resource’s connection details Secret contains all the expected credentials:

1kubectl get secret -n default -l crossplane.io/composite=my-keys -o jsonpath='{.items[0].data}' | jq

You should see output like this:

1{
2  "password-0": "<base64-encoded-password>",
3  "password-1": "<base64-encoded-password>",
4  "user-0": "<base64-encoded-username>",
5  "user-1": "<base64-encoded-username>"
6}

Decode one of the values to verify it contains the expected data:

1kubectl get secret -n default -l crossplane.io/composite=my-keys -o jsonpath='{.items[0].data.user-0}' | base64 -d

Understanding how composing connection details works

The basic steps to expose connection details for a composite resource are:

  1. Compose resources: Create composed resources as usual in your composition, such as IAM User and AccessKeys. These resources expose their connection details in a Secret.

  2. Set writeConnectionSecretToRef: Each composed resource that should have connection details stored in their own individual Secret should have their writeConnectionSecretToRef set in the composition.

  3. Observed connection details: Crossplane observes the actual state of each composed resource, including its connection details, and makes this data available when it runs the function.

  4. Compose the combined Secret: With the observed connection details of your composed resources in hand, compose a Secret resource that combines the important connection details you want to expose for the XR. Consider allowing the consumer of the XR to specify the name they want this secret to have.

  5. Handle transient state: When your XR is first created, the composed resources and/or their connection details may not exist yet. Your Composition should handle these cases by checking if resources and their connection details exist before accessing them.

Troubleshooting

Composite resource’s connection details Secret is empty

Causes:

  • Composed resources don’t have writeConnectionSecretToRef set
  • Composed resources aren’t ready/healthy yet
  • Not handling initial nil state correctly in the Composition

Solutions:

  • Verify writeConnectionSecretToRef is set on all composed managed resources
  • Wait for composed resources to become ready (kubectl get and check the READY column)
  • Verify the composed resource is actually producing connection details: kubectl get secret <composed-resource-secret-name> -o yaml
  • Add nil/empty checks in your Composition logic to safeguard access to data that may not exist yet

Connection details aren’t encoded correctly

Cause: Not encoding the combined secret data correctly in your Composition logic

Solution: Ensure that your connection details data is correctly encoded for the function you’re using. For example, function-python requires you to convert connection details to base64-encoded strings, while connection details in function-go-templating and function-kcl are already encoded this way and require no conversion logic.

Secret has an empty namespace

Cause: Not setting the namespace of the Secret for a Cluster scoped XR, resulting in an error message like an empty namespace may not be set when a resource name is provided

Solution: When Cluster scoped XRs compose namespace-scoped resources like a Secret, you must explicitly set a namespace on the resource. Consider allowing the XR consumer to specify the namespace value in your composition. Namespaced XRs don’t have this problem because Crossplane defaults any composed resource’s namespace to the XR’s namespace if left empty.

Clean up

Delete the composite resource to clean up:

1kubectl delete -f my-keys.yaml

When you delete the composite resource, Crossplane deletes:

  • The composed IAM User and AccessKeys from AWS
  • The individual Secrets from composed resources
  • The composite resource’s connection details Secret
Important
Make sure to delete your composite resources before uninstalling the provider or shutting down your control plane. If those are no longer running, they can’t clean up composed resources and you would need to delete them manually.

Learn more