Crossplane providers and compositions are essentially just Kubernetes Custom Resources, and testing them end-to-end means running your actual Crossplane control plane against a real Kubernetes cluster and watching it provision and manage cloud resources.
Here’s a breakdown of how to set up those tests, focusing on a practical, repeatable approach.
The Core Idea: Treat Crossplane Like Any Other Application
At its heart, Crossplane is a Kubernetes operator. Your providers are also operators, and your Compositions are just CRDs that define how Crossplane should behave. Therefore, testing them end-to-end means deploying Crossplane, its providers, and your custom resources to a Kubernetes cluster and verifying that the intended cloud resources are created and managed as expected.
Setting Up Your Test Environment
You need a Kubernetes cluster. For local development, kind (Kubernetes in Docker) is excellent. For CI/CD, a managed Kubernetes service like GKE, EKS, or AKS is more representative.
-
Provision a Kubernetes Cluster:
kind(Local):
This spins up a lightweight Kubernetes cluster entirely within Docker containers. It’s fast and isolated.kind create cluster --name crossplane-test --config - <<EOF kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane EOF- Managed Kubernetes (CI/CD):
Use your cloud provider’s CLI (e.g.,
gcloud container clusters create,aws eks create-cluster,az aks create) to provision a cluster. Ensure it has enough resources for Crossplane, your providers, and the resources they manage.
-
Install
kubectlandhelm: These are essential for interacting with your cluster. Make sure they are in your PATH. -
Install Crossplane: You can install Crossplane using Helm. This is the most common and recommended way.
helm install crossplane crossplane-stable/crossplane \ --namespace crossplane-system \ --create-namespace \ --version 1.15.0 # Use a specific, stable versionThis deploys the Crossplane core components into the
crossplane-systemnamespace. -
Install Your Provider(s): Providers are also installed as Custom Resources. You can apply their YAML manifests directly or use Helm charts if available.
For example, to install the AWS provider:
kubectl apply -f https://raw.githubusercontent.com/crossplane/provider-aws/v0.55.0/package/crossplane.yaml(Note: Always check the latest provider version and its installation method on its respective GitHub repository.)
-
Configure Provider Credentials: This is critical. Your provider needs credentials to talk to the cloud API. This is done by creating a
ProviderConfigcustom resource. The exact configuration depends on the provider.For AWS, you’d typically create a Kubernetes Secret containing your AWS credentials and then reference it in the
ProviderConfig:# Example AWS ProviderConfig apiVersion: aws.crossplane.io/v1beta1 kind: ProviderConfig metadata: name: default # or any name you prefer spec: credentials: source: Secret secretRef: namespace: crossplane-system name: aws-credentials # This secret must exist and contain AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEYYou’d create the
aws-credentialssecret like this:kubectl create secret generic aws-credentials \ --from-literal=AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY \ --from-literal=AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY \ --namespace crossplane-systemCrucially, ensure these credentials have the necessary permissions to create and manage the cloud resources your Compositions will define.
Writing and Applying Your Compositions and Claims
Now you write your Compositions and Claims, just as you would for any Crossplane deployment.
-
Define Your Composition: This is a Kubernetes Custom Resource that tells Crossplane how to provision cloud resources from a set of managed resources.
# Example Composition for a managed PostgreSQL instance apiVersion: apiextensions.crossplane.io/v1 kind: Composition metadata: name: managedpostgresqls.mycompany.com labels: testing: true # Label to easily select this composition spec: compositeTypeRef: apiVersion: example.mycompany.com/v1alpha1 kind: ManagedPostgreSQL resources: - name: postgresql-instance base: apiVersion: rds.aws.upbound.io/v1beta1 # Using upbound provider for simplicity here kind: DBInstance spec: forProvider: region: us-east-1 dbInstanceClass: db.t3.micro engine: postgres engineVersion: "14.5" # Note: The ProviderConfig reference is implicitly handled by Crossplane # if there's a single ProviderConfig that matches the provider. # Explicitly, it would look like: # providerConfigRef: # name: default patches: - fromFieldPath: spec.parameters.storageGB toFieldPath: spec.forProvider.allocatedStorage - fromFieldPath: spec.parameters.version toFieldPath: spec.forProvider.engineVersion -
Define Your Composite Resource Definition (XRD): This defines the schema for your composite resource (e.g.,
ManagedPostgreSQL).apiVersion: apiextensions.crossplane.io/v1 kind: CompositeResourceDefinition metadata: name: managedpostgresqls.example.mycompany.com spec: group: example.mycompany.com names: kind: ManagedPostgreSQL plural: managedpostgresqls claimNames: kind: PostgreSQL plural: postgresqls versions: - name: v1alpha1 served: true referenceable: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: parameters: type: object properties: storageGB: type: integer version: type: string required: - parameters -
Apply Your XRD and Composition: Apply these to your cluster.
kubectl apply -f your-xrd.yaml kubectl apply -f your-composition.yaml -
Create a Claim: This is what your application developers would create. It references the
claimkind defined in your XRD and specifies the desired parameters.# Example Claim for a PostgreSQL instance apiVersion: example.mycompany.com/v1alpha1 kind: PostgreSQL metadata: name: my-app-db spec: parameters: storageGB: 20 version: "14.5" compositionSelector: # Optional, but good for testing matchLabels: testing: trueApply this claim:
kubectl apply -f your-claim.yaml
Verification and Assertions
This is where the "end-to-end" part truly shines. You’re not just checking if Crossplane thinks it did something; you’re checking if the cloud resource actually exists.
-
Check Kubernetes Status: First, verify that Crossplane has created the managed resources and that they are in a
Readystate.# Check the claim kubectl get managedpostgresqls my-app-db -o yaml # Check the composite resource (created by Crossplane from the claim) kubectl get postgresqls.example.mycompany.com my-app-db -o yaml # Check the actual managed resource (e.g., AWS RDS DBInstance) kubectl get dbinstances.rds.aws.upbound.io my-app-db-postgresql-instance -n crossplane-system -o yamlLook for
status.atProvider.dbInstanceStatus: availableor similar indicators of success in the managed resource’s status. -
Check Cloud Provider Console/CLI: This is the ultimate validation. Log into your AWS, GCP, or Azure console and verify that the actual database instance, storage bucket, or Kubernetes cluster has been provisioned with the correct settings.
For AWS RDS:
aws rds describe-db-instances --db-instance-identifier <your-db-instance-id> --query "DBInstances[0].DBInstanceStatus"The output should be
available. -
Testing Updates and Deletions:
- Updates: Modify your
Claim(e.g., changestorageGB) and re-apply. Verify that the Kubernetes status updates and that the cloud resource reflects the changes. - Deletions: Delete the
Claim. Verify that the Kubernetes managed resources are cleaned up and that the cloud resource is also deleted.
kubectl delete managedpostgresqls my-app-db # Then check that the DBInstance CR and the actual RDS instance are gone. - Updates: Modify your
The Secret Sauce: Dynamic Resource Management
What’s often overlooked is how Crossplane’s reconciliation loop continuously monitors the desired state (defined in your Claims and Compositions) against the actual state in the cloud. When you apply a Claim, Crossplane:
- Finds a matching
Composition(based oncompositeTypeRefandcompositionSelector). - Uses the
Composition’sresourcesandpatchesto generate the necessary Kubernetes Custom Resources for managed resources (e.g.,DBInstance). - The
Provider(e.g.,provider-aws) watches these managed resource CRs. - The
Providerinteracts with the cloud API (AWS RDS API in this case) to create, update, or delete the actual cloud resource. - It then watches the cloud resource’s state and updates the Kubernetes managed resource’s
statusfield. - Crossplane propagates this status back up to your
Compositeresource (and ultimately, yourClaim).
This continuous loop means your tests should not just check for immediate creation but also for eventual consistency and responsiveness to changes.
The next step in your testing journey is likely to explore testing Crossplane’s built-in policies for compliance and governance.