Skip to main content

Crossplane Ep 6: Patching and Transforms

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
crossplane - This article is part of a series.
Part 6: This Article
Hardcoded Compositions are useless in production. If a developer asks for a “Production” database, they need 500GB of storage. If they ask for a “Dev” database, they only need 20GB. We must bridge the gap between the Developer’s API request (the XR) and the physical AWS infrastructure (the MR) using Patches and Transforms.

1. What is a Patch?
#

In Crossplane, a Patch is an instruction inside a Composition that tells the reconciliation engine to copy a value from one place and paste it into another.

There are four primary types of patches:

  1. FromCompositeFieldPath: (The most common). Takes data from the XR (the Developer’s request) and writes it into the MR (AWS).
  2. ToCompositeFieldPath: Takes data from the physical AWS MR (like an auto-generated VPC ID) and writes it up to the XR, so the Developer can see it.
  3. CombineFromComposite: Takes multiple fields from the XR, concatenates them together, and writes them to the MR.
  4. Environment: Pulls data from a global cluster environment configuration (covered in Episode 7).

2. Implementing a Basic Patch
#

Let’s fix the Composition we wrote in Episode 5. We want the storageGB parameter from the developer’s Claim to dictate the allocatedStorage on the AWS RDS instance.

Open your composition-postgres.yaml and add a patches block to the rds-instance resource:

  resources:
    - name: rds-instance
      base:
        apiVersion: rds.aws.upbound.io/v1beta1
        kind: Instance
        spec:
          forProvider:
            # We REMOVE allocatedStorage from the base entirely!
            region: us-east-1
            engine: postgres
            instanceClass: db.t3.micro
      
      # 1. Add the Patches Block
      patches:
        # 2. Define the Patch
        - type: FromCompositeFieldPath
          # 3. Where is the data coming from? (Inside the XR)
          fromFieldPath: spec.parameters.storageGB
          # 4. Where is the data going? (Inside the MR)
          toFieldPath: spec.forProvider.allocatedStorage

How it Works
#

  1. The developer submits a claim with spec.parameters.storageGB: 50.
  2. Crossplane evaluates the Composition.
  3. The Patcher reads the value 50 from the XR.
  4. It injects allocatedStorage: 50 into the base of the MR before sending it to the AWS Provider.

3. What is a Transform?
#

A Patch moves data 1-to-1. But what if the data types don’t match, or you need to run logic?

For example, the Developer requests environment: prod. But the AWS RDS API doesn’t know what “prod” means. AWS expects an instanceClass string, like db.m5.large.

We must Transform the input data while it is in transit during the Patch.

The Map Transform
#

The map transform acts like a switch statement in programming.

Let’s update our patches block to handle the environment string:

      patches:
        - type: FromCompositeFieldPath
          fromFieldPath: spec.parameters.environment
          toFieldPath: spec.forProvider.instanceClass
          
          # Add a Transform!
          transforms:
            - type: map
              map:
                # If XR says "dev", send "db.t3.micro" to AWS
                dev: db.t3.micro
                # If XR says "prod", send "db.m5.large" to AWS
                prod: db.m5.large

The String Transform
#

Often, you need to enforce naming conventions. If a developer names their database my-db, you might want the physical AWS bucket to be named acmecorp-my-db-prod.

You can use a string transform with fmt (Go-style string formatting):

        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: spec.forProvider.identifier
          transforms:
            - type: string
              string:
                fmt: "acmecorp-%s-db"

If the XR is named auth-service, the AWS RDS identifier will become acmecorp-auth-service-db.


4. Bi-Directional Patching (ToComposite)
#

So far, we have only passed data down from the XR to the MR. But what happens after the database boots? AWS will dynamically assign an Endpoint URL. The application developer needs that URL!

We must patch data up from AWS to the XR.

First, you must update your XRD (xrd-postgres.yaml) to define a place to hold the output data:

# Inside xrd-postgres.yaml -> openAPIV3Schema -> properties
          status:
            type: object
            properties:
              connectionUrl:
                type: string
                description: "The live AWS endpoint for the database."

Then, in your Composition, add a ToCompositeFieldPath patch:

      patches:
        - type: ToCompositeFieldPath
          # Read from the physical AWS MR status
          fromFieldPath: status.atProvider.endpoint
          # Write to the XR status
          toFieldPath: status.connectionUrl

Now, the Application Developer can run kubectl describe postgresqlinstance app-db and they will see the live AWS Endpoint URL printed in the status of their Claim!


Troubleshooting & Common Errors
#

  1. cannot apply patch: invalid type

    • Root Cause: You tried to patch a string from the XR into an integer field in the MR (or vice versa).
    • Solution: Use a convert transform inside your patch block to explicitly cast the variable (e.g., transforms: [{ type: convert, toType: int }]).
  2. The map transform returns an empty value

    • Root Cause: The developer provided a value that doesn’t exist in your map dictionary, and you did not provide a fallback.
    • Solution: Always validate the input in your XRD using enum, or provide a default value in your Composition using the math or map fallback properties.

Conclusion & Next Steps
#

Patches and Transforms are the programming logic of Crossplane Compositions. By mastering them, you can build incredibly complex, intelligent vending machines that translate simple developer intents into robust cloud architectures.

However, writing region: us-east-1 50 times across 10 different Compositions is a massive violation of the DRY (Don’t Repeat Yourself) principle. What happens if the company decides to migrate to us-west-2? You would have to rewrite every YAML file.

In Episode 7: Environment Configs, we will learn how to extract global variables into cluster-wide configurations, allowing your Compositions to dynamically adapt based on the Kubernetes cluster they are deployed in.

crossplane - This article is part of a series.
Part 6: This Article