Skip to content

crossplane-contrib/function-patch-and-transform

Repository files navigation

function-patch-and-transform

CI GitHub release (latest SemVer)

This composition function does everything Crossplane's built-in patch & transform (P&T) composition does. Instead of specifying spec.resources in your Composition, you can use this function.

Using this function, P&T looks like this:

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: example
spec:
  # Omitted for brevity.
  mode: Pipeline
  pipeline:
  - step: patch-and-transform
    functionRef:
      name: function-patch-and-transform
    input:
      apiVersion: pt.fn.crossplane.io/v1beta1
      kind: Resources
      resources:
      - name: bucket
        base:
          apiVersion: s3.aws.upbound.io/v1beta1
          kind: Bucket
          spec:
            forProvider:
              region: us-east-2
        patches:
        - type: FromCompositeFieldPath
          fromFieldPath: "spec.location"
          toFieldPath: "spec.forProvider.region"
          transforms:
          - type: map
            map: 
              EU: "eu-north-1"
              US: "us-east-2"

Notice that it looks very similar to native P&T. The difference is that everything is under spec.pipeline[0].input.resources, not spec.resources. This is the Function's input.

Okay, but why?

There are a lot of good reasons to use a function to use a function to do P&T composition. In fact, it's so compelling that the Crossplane maintainers are considering deprecating native P&T. See Crossplane issue #4746 for details.

Mix and match P&T with other functions

With this function you can use P&T with other functions. For example you can create a desired resource using the Go Templating function, then patch the result using this function.

To include results from previous functions, simply provide a resources entry for each and specify a name field that matches the name of the resource from the previous function. Also, do not specify any value for the base field of each resource.

It's not just patches either. You can use P&T to derive composite resource connection details from a resource produced by another function, or use it to determine whether a resource produced by another function is ready.

A straightforward example for multistep mix and match pipeline with function-patch-and-transform and function-go-templating can be found here

Decouple P&T development from Crossplane core

When P&T development happens in a function, it's not coupled to the Crossplane release cycle. The maintainers of this function can cut releases more frequently to add new features to P&T.

It also becomes easier to fork. You could fork this function, add a new kind of transform and try it out for a few weeks before sending a PR upstream. Or, if your new feature is controversial, it's now a lot less work to maintain your own fork long term.

Test P&T locally using the Crossplane CLI

You can use the Crossplane CLI to run any function locally and see what composed resources it would create. This only works with functions - not native P&T.

For example, using the files in the example directory:

$ crossplane beta render xr.yaml composition.yaml functions.yaml

Produces the following output, showing what resources Crossplane would compose:

---
apiVersion: example.crossplane.io/v1
kind: XR
metadata:
  name: example-xr
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
  annotations:
    crossplane.io/composition-resource-name: bucket
  generateName: example-xr-
  labels:
    crossplane.io/composite: example-xr
  ownerReferences:
    # Omitted for brevity
spec:
  forProvider:
    region: us-east-2

See the composition functions documentation to learn how to use crossplane beta render.

Differences from the native implementation

This function has a few small, intentional breaking changes compared to the native implementation.

New Required fields

These fields are now required. This makes P&T configuration less ambiguous:

  • resources[i].name
  • resources[i].connectionDetails[i].name
  • resources[i].connectionDetails[i].type
  • resources[i].patches[i].transforms[i].string.type
  • resources[i].patches[i].transforms[i].math.type

mergeOptions replaced by toFieldPath

Also, the resources[i].patches[i].policy.mergeOptions field is no longer supported. This functionality has been replaced by the resources[i].patches[i].policy.toFieldPath field. The table below outlines previous behavior that was possible with mergeOptions and how to achieve it with the new toFieldPath field:

# mergeOptions appendSlice keepMapValues toFieldPath
1 nil N/A N/A nil which defaults to Replace
2 non-nil nil or false true MergeObjects
3 non-nil true nil or false ForceMergeObjectsAppendArrays
4 non-nil nil or false nil or false ForceMergeObjects
5 non-nil true true MergeObjectsAppendArrays

As an example, a previous configuration using the no longer supported mergeOptions:

policy:
  mergeOptions:
    appendSlice: true
    keepMapValues: true

Should be replaced with:

policy:
  toFieldPath: MergeObjectsAppendArrays

Starting with Crossplane v1.16.0, the convert command in the Crossplane CLI will automatically convert mergeOptions to toFieldPath for you.

Developing this function

This function uses Go, Docker, and the Crossplane CLI to build functions.

# Run code generation - see input/generate.go
$ go generate ./...

# Run tests - see fn_test.go
$ go test ./...

# Build the function's runtime image - see Dockerfile
$ docker build . --tag=runtime

# Build a function package - see package/crossplane.yaml
$ crossplane xpkg build -f package --embed-runtime-image=runtime