Connection Details

This page will try to document the various layers of connection details and how to handle them.

Please read Crossplane’s docs about this topic first. It explains the various connection details very well. The following page assumes you’ve read and understand.

Composite Resource Connection Details

These are the most straight forward kind of connection details to handle. They just take a name and a value, which will then be exposed on the connection details of the claim.

svc.SetConnectionDetail(PostgresqlUrl, []byte(val))

Managed Resource Connection Details

Each manged resource can optionally expose its own connection details. Some examples:

  • Exoscale IAM

  • Minio User

  • Helm Release

Let’s have a look at an easy example and a more complicated one.

Simple Example

We use the minio User managed resource in this example. The User can expose two connection details: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. It’s important that we set the WriteConnectionSecretToReference in the composition.

user := &miniov1.User{
  ...
	Spec: miniov1.UserSpec{
    Spec: ...
		ResourceSpec: xpv1.ResourceSpec{
			WriteConnectionSecretToReference: &xpv1.SecretReference{
				Name:      "uniqname", (1)
				Namespace: "secretnamespace", (2)
			},
		},
	},
}
1 Unique name for this secret, usually the composite name + some suffix
2 The namespace where the secret should be saved, usually set to writeConnectionSecretsToNamespace defined in the composition.

It depends on how the User object is used afterward to determine where to save the connection details.

  1. It’s a pure bucket for the end user. Then we can save the connection details in the writeConnectionSecretsToNamespace namespace. It will be made available for the end-user via the claim’s writeConnectionSecretToRef.

  2. It’s used as part of service. If the service needs a bucket for backups or other uses, then we need to save the connection details in a namespace where the service can access it (instance namespace). In such a case the connection details should be treated as an internal secret and not be exposed to the user. This can be achieved by not specifying the keys in the composite’s connectionSecretKeys.

For this example we assume the first case. To actually expose the secrets we need to use xfnv1alpha1.DerivedConnectionDetail. These are usually indirect connection details, but they can also take the value directly.

It’s possible to reference connection details that are exposed via the managed resource, or any field path within the managed resources, like status fields. Crossplane Composition Function in version 1.14 automatically add the connection details to the observed objects. This makes it very easy to get and manipulate them.

// Get all connection details of a resurce as a map.
cd, _ := svc.GetObservedComposedResourceConnectionDetails(comp.Name + "-release")

mySecret := cd["AWS_ACCESS_KEY_ID"]

// Automatically add all connection details of a resource to the composite.
err = svc.AddObservedConnectionDetails(comp.Name + "-release")
if err != nil {
	return err
}

Lastly, the User xrd needs to be made aware of the keys it should expose in the claim connection details. This is done in component-appcat.

local connectionSecretKeys = [
  'AWS_ACCESS_KEY_ID',
  'AWS_SECRET_ACCESS_KEY',
];

local xrd = xrds.XRDFromCRD(
  ...
  connectionSecretKeys=connectionSecretKeys,
);

Helm example

These connection details differ a bit from normal managed resources, as the provider cannot know what secrets every available helm chart can expose.

Instead, provider-helm exposes a flexible way to specify exactly which of the helm managed object contain the relevant values.

Let’s assume our given helm chart writes a secret and a service. Both of them contain information relevant to the user to access the service.

r := &v1beta1.Release{
		Spec: v1beta1.ReleaseSpec{
			ResourceSpec: xpv1.ResourceSpec{
				WriteConnectionSecretToReference: &xpv1.SecretReference{ (1)
					Name: "myname",
					Namespace: "secretnamespace",
				},
			},
			ConnectionDetails: []v1beta1.ConnectionDetail{
				{
					ObjectReference: corev1.ObjectReference{
						APIVersion: "v1",
						Kind:      "Secret",
						Name:      "mysecret",
						Namespace: "myns",
						FieldPath: "data.my-password", (2)
					},
					ToConnectionSecretKey: "connectionPasswordKey",
				},
				{
					ObjectReference: corev1.ObjectReference{
						APIVersion : "v1"
						Kind:      "Service",
						Name:      "myservice",
						Namespace: "myns",
						FieldPath: "spec.ports[0].port",
					},
					ToConnectionSecretKey: "connectionPortKey"
				},
			},
		},
	}
1 The connection detail still needs to be written somewhere.
2 We don’t have to bother with base64 encoding, provider-helm will handle this for us.

With this method it should be easier to handle most cases. However, provider-helm’s connection detail management isn’t perfect. It cannot handle any transformations on the secrets. So if anything needs to concatenated, split, or changed in any other way, then this might unfortunately not be the way to do it.

These connection details then still need to be added to the composite.

err = svc.AddObservedConnectionDetails(comp.Name + "-release")
if err != nil {
	return err
}

Unmanaged Connection Details

There are cases where the connection details are in objects that aren’t managed by the composition function or any of the providers. For example if the service is provisioned via an operator.

As of version v0.8.0 of provider-kubernetes, it supports exposing connection details directly from the Object. It works similar to how provider-helm can expose connection details. Also like with provider-helm, it’s possible to expose any field of any arbitrary k8s object that the provider is allowed to read.

  1. Deploy an operator’s cr

secret := &opv1.MyThing{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "fancything",
			Namespace: "myns",
		},
		Spec: {
			ForProvider: // Omitted
			// Let's assume that the CR provisions some service and writes a service and secret that we need for connection details.
			ConnectionDetails: []v1beta1.ConnectionDetail{
				{
					ObjectReference: corev1.ObjectReference{
						APIVersion: "v1",
						Kind:      "Secret",
						Name:      "mysecret",
						Namespace: "myns",
						FieldPath: "data.my-password", (2)
					},
					ToConnectionSecretKey: "connectionPasswordKey",
				},
				{
					ObjectReference: corev1.ObjectReference{
						APIVersion : "v1"
						Kind:      "Service",
						Name:      "myservice",
						Namespace: "myns",
						FieldPath: "spec.ports[0].port",
					},
					ToConnectionSecretKey: "connectionPortKey"
				},
			},
		}
	}

	return svc.SetDesiredKubeObserveObject(secret, comp.Name+"-my-cr")

The connection details can then be read like usual.

  1. Read connectiondetail

secret := &xkube.Object{}
cd, _ := svc.GetObservedComposedResourceConnectionDetails(comp.Name + "-my-cr")

Also, here for secrets the values will not be base64 encoded, so they can be used directly.

svc.SetConnectionDetail("MY_PASSWORD", cd["connectionPasswordKey"])