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. In terms of go code they are specified by xfnv1alpha1.ExplicitConnectionDetail. As the name suggests, they are explicit and take a name and a value.

iof.Desired.PutCompositeConnectionDetail(ctx, xfnv1alpha1.ExplicitConnectionDetail{
	Name:  "ENDPOINT",
	Value: bucket.Status.Endpoint,
})

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. With composition functions the ConnectionDetailTypeFromConnectionSecretKey is probably most commonly used, as field values can easily be accessed and set as an xfnv1alpha1.ExplicitConnectionDetail in the composite.

cd := []xfnv1alpha1.DerivedConnectionDetail{
	{
		Name:                    pointer.String(secretKeyName),
		FromConnectionSecretKey: pointer.String(secretKeyName),
		Type:                    xfnv1alpha1.ConnectionDetailTypeFromConnectionSecretKey,
	},
	{
		Name:                    pointer.String(accessKeyName),
		FromConnectionSecretKey: pointer.String(accessKeyName),
		Type:                    xfnv1alpha1.ConnectionDetailTypeFromConnectionSecretKey,
	},
}

return iof.Desired.PutWithResourceName(ctx, user, "minio-user", runtime.AddDerivedConnectionDetails(cd))

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 as derived connectiondetails.

cd := []xfnv1alpha1.DerivedConnectionDetail{
	{
		Name:                    pointer.String(connectionPasswordKey),
		FromConnectionSecretKey: pointer.String(connectionPasswordKey),
		Type:                    xfnv1alpha1.ConnectionDetailTypeFromConnectionSecretKey,
	},
	{
		Name:                    pointer.String(connectionPortKey),
		FromConnectionSecretKey: pointer.String(connectionPortKey),
		Type:                    xfnv1alpha1.ConnectionDetailTypeFromConnectionSecretKey,
	},
}

return iof.Desired.PutWithResourceName(ctx, release, "minio-release", runtime.AddDerivedConnectionDetails(cd)

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. Also, if the given methods to acquire the connection details aren’t powerful enough, for example if some helm release connection details need some transformations.

To still get access to any information needed, an observer object can be leveraged. This uses provider-kubernetes to deploy an Object with the Observe policy. These objects will only be read and never modified, making it perfect for this use-case.

However this involes two steps, first to deploy the observer and then in the next reconcile to read it.

  1. Deploy observer

secret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "mysecret",
			Namespace: "myns",
		},
	}

	return iof.Desired.PutIntoObserveOnlyObject(ctx, secret, comp.Name+"-secret-observer")
  1. Read observer

secret := &corev1.Secret{}
err = iof.Observed.Get(ctx, secret, comp.Name+"-secret-observer")
if err != nil {
  if err == runtime.ErrNotFound { (1)
    return runtime.NewNormal()
  }
	return runtime.NewFatalErr(ctx, "cannot get observed deletion job", err)
}
1 We need to ignore not found errors here, as the observer will not exist during the first reconcile

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

iof.Desired.PutCompositeConnectionDetail(ctx, v1alpha1.ExplicitConnectionDetail{
	Name:  "MY_PASSWORD",
	Value: string(secret.Data["my_password"])
})