Build with Go Templating

Upbound supports defining your control plane APIs using YAML with Go templates.

Go templating functions can make use of all built-in Go templating features and anything supported by Crossplane’s function-go-templating.

You can organize templates into multiple files with an arbitrary directory structure, as long as all files have the .gotmpl file extension. Templates are read in lexical order and concatenated with YAML separators (---) between them.

Prerequisites

To define your control plane APIs using Go templating you need the YAML Visual Studio Code extension and optionally the Modelines extension. Refer to the Visual Studio Code Extensions documentation to learn how to install them.

Example

The following example function composes an S3 bucket based on a simplified bucket XRD.

The 01-compose.yaml.gotmpl file below takes a composite resource (XR) as input. It produces a Bucket managed resource (MR) from the S3 provider based on its parameters.

# code: language=yaml
# yaml-language-server: $schema=../../.up/json/models/index.schema.json

---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
  annotations:
    {{ setResourceNameAnnotation "bucket" }}
spec:
  forProvider:
    region: "{{ $xr.spec.parameters.region }}"

Expand the example below to see a more advanced Go templating function.

The files below take a composite resource (XR) as input and produces managed resources (MRs) from the S3 provider based on its parameters. The templates are split across multiple files for readability.

The function always composes an S3 bucket. When the S3 bucket exists, it also composes a bucket access control list (ACL). The ACL references the bucket by name.

If the composite resource’s spec.versioning field is true, the function enables versioning by composing a bucket versioning configuration. Like the ACL, the versioning configuration references the bucket by name.

As a best practice, place any Go templating flow control (if statements, loops, variable assignments, etc.) in YAML comments, as demonstrated below. While not required, this avoids confusing the YAML language server with non-YAML syntax, leading to a better editor experience.

00-prelude.yaml.gotmpl

This file contains generated boilerplate to place the observed composite resource and its parameters in Go templating variables, for use by subsequent templates.

#{{ $xr := getCompositeResource . }}
#{{ $params := $xr.spec.parameters }}

01-bucket.yaml.gotmpl

This file creates the Bucket MR. It then checks whether Crossplane has assigned an external name for the bucket and places the name in a variable.

# code: language=yaml
# yaml-language-server: $schema=../../.up/json/models/index.schema.json

---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
  annotations:
    {{ setResourceNameAnnotation "bucket" }}
spec:
  forProvider:
    region: "{{ $params.region }}"

# The desired ACL, encryption, and versioning resources all need to refer to the
# bucket by its external name, which is stored in its external name
# annotation. Fetch the external name into a variable so subsequent templates
# can use it.
#
#{{ $bucket := getComposedResource . "bucket" }}
#{{ $bucket_external_name := "" }}
#{{ if $bucket }}
#{{ $bucket_external_name = get $bucket.metadata.annotations "crossplane.io/external-name" }}
#{{ end }}

02-acl.yaml.gotmpl

This file creates the BucketACL MR. Since this MR requires the bucket’s external name, the template produces the MR only when the external name is available.

# code: language=yaml
# yaml-language-server: $schema=../../.up/json/models/index.schema.json

# Don't create the ACL until the bucket name is available.
#{{ if $bucket_external_name }}
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: BucketACL
metadata:
  annotations:
    {{ setResourceNameAnnotation "acl" }}
spec:
  forProvider:
    region: "{{ $params.region }}"
    bucket: "{{ $bucket_external_name }}"
    acl: "{{ $params.acl }}"
#{{ end }}

03-versioning.yaml.gotmpl

This file creates the BucketVersioning MR when the XR has versioning enabled. Like the BucketACL, this MR requires the bucket’s external name, so the template produces the MR only after the external name is available.

# code: language=yaml
# yaml-language-server: $schema=../../.up/json/models/index.schema.json

#{{ if $params.versioning }}
# Don't create the BucketVersioning until the bucket name is available.
#{{ if $bucket_external_name }}
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: BucketVersioning
metadata:
  annotations:
    {{ setResourceNameAnnotation "versioning" }}
spec:
  forProvider:
    region: "{{ $params.region }}"
    bucket: "{{ $bucket_external_name }}"
    versioningConfiguration:
      - status: Enabled

#{{ end }}
#{{ end }}

Control plane project model

The Upbound programming model defines the core concepts you can use when creating your control plane using Upbound.

Upbound builds embedded Go templating functions on top of Crossplane’s function-go-templating, offering a simplified, Upbound-specific development experience.