Steven Borrelli

Go

May 22, 2025

May 22, 2025

Read time: 0 mins

Read time: 0 mins

Testing Upbound's New Go Text Template integration with Visual Studio Code

Testing Upbound's New Go Text Template integration with Visual Studio Code

Share

Share

Testing Upbound's New Go Text Template integration with Visual Studio Code

Testing Upbound's New Go Text Template integration with Visual Studio Code

Upbound now supports Go templates for control planes with VS Code, offering schema validation, autocompletion, and testing for platform engineers.

Upbound provides a powerful development platform for Platform Engineers with VSCode integration, autocompletion, schema validation, artifact builds, and testing.

Included in version 0.39.0 of the up CLI, Upbound has extended this rich developer experience to Go Templates, alongside the existing support for Go, KCL, and Python.

Go Templates are used in many infrastructure tools like Helm and in one of the most popular Crossplane functions, function-go-templating. A Go Template to define an Azure Resource Group mixes YAML with variables and functions surrounded by double-brace characters {{ }}:

apiVersion: azure.upbound.io/v1beta1
kind: ResourceGroup
metadata:
  labels:
    azure.platform.upbound.io/network-id: {{ $parameters.id }}
  name: {{ (print $parameters.id  "-rg") }}
spec:
  deletionPolicy: {{ $parameters.deletionPolicy }}
  forProvider:
    location: {{ $parameters.region }}
  providerConfigRef:
    name

 In this blog, we'll port an existing Composition to a Go Template, covering dependencies, schema generation, rendering, and testing.

Prerequisites

You'll need a minimum version v0.39.0 of Upbound's up binary. Download instructions are available at  https://docs.upbound.io/operate/cli/. 

Run up version to confirm you have the binary installed and at the proper release:

$ up

You'll also need a Crossplane environment, either running in a Kubernetes cluster or in a local development environment like kind. See the Crossplane installation guide for more information.

Upbound provides free development Control Planes that delete after 24 hours see build-with-upbound that can also be used for development and testing.

Visual Studio Code Extensions
To enable the best developer experience with Go Templates, several VSCode Extensions should be installed:

Porting an Existing KCL Function To Go Templating

Let's take an existing Upbound Control Plane Project and rewrite an Embedded Function in Go Templating. This way we can reuse the existing tests to ensure we are not introducing any breaking changes.

Upbound's configuration-azure-network has one function that is written in KCL: main.k. The KCL function creates an Azure Resource Group, Virtual Network, and one or more Subnets based on what the end user requests.

Every Control Plane Project contains several directories and files:

  • apis Where Platform APIs are defined

  • examples Example manifests for documentation and testing

  • functions Embedded Composition Functions where resources are defined

  • tests Composition and End-to-End (e2e) Tests

  • upbound.yaml a Project definition file used to track dependencies and name the artifacts


Clone the repo and enter the configuration-azure-network directory:

git clone https://github.com/upbound/configuration-azure-network.git
cd

Generate a New Composition

An Embedded Function contains the runtime and infrastructure code in a Docker (OCI) image. The Composition contains pointers to the function images that are to be run in a series of steps.

The existing Azure network composition.yaml has a functionRef to a function written in KCL. Let's generate a new Composition for our Go Template function using the existing API definition.

With the --name CLI option, we can generate multiple Compositions that support the same Platform API.


$ up composition generate apis/xnetworks/definition.yaml --name

The apis/xnetworks directory now contains the existing and new composition files:


$ tree apis/xnetworks
apis/xnetworks
├── composition-go-templating.yaml
├── composition.yaml
└── definition.yaml

1 directory, 3

Generate the Function

Now that the Composition is defined, generate a Function with the --language CLI option set to go-templating, and pointing at the Composition that was just generated.

 

$ up function generate xnetwork-go-templating apis/xnetworks/composition-go-templating.yaml --language=go-templating
  ✓   Checking dependencies                                                                                 
  ✓   Generating Function Folder                                                                            
  ✓   Adding Pipeline Step in

This will create .gotmpl files in the functions/<function-name> directory:

$ tree functions/xnetwork-go-templating
functions/xnetwork-go-templating
├── 00-prelude.yaml.gotmpl
└── 01-compose.yaml.gotmpl

1 directory, 2

The .gotmpl files are compatible with function-go-templating templates, and the machinery in the up binary generates schemas for type checks and autocompletion.

Multiple .gotmpl files can be stored in the function directory. During a project build they are concatenated and packaged into a runnable Docker image.

Added to the Composition apis/xnetworks/composition-go-templating.yaml file will be a Pipeline step pointing to the function. The name of the function is based on values in the upbound.yaml Project file and the function name.

 pipeline:
  - functionRef:
      name: upbound-configuration-azure-networkxnetwork-go-templating
    step

The First Build

To generate our schemas, up uses the dependencies in the upbound.yaml project file. Running up project build generates schemas and packages the APIs, Compositions and Functions into a Package that can be pushed to any Docker-compatible registry:

$ up

A hidden directory .up is created or updated every time up project build is run. This directory contains the multi-language schemas for the project dependencies. A typical development workflow is to add dependencies to the project and build it.

Azure ResourceGroups are part of the base "family" Azure Provider. 

$ up

Go Templates use the models stored in the json directory.

 

$ tree -L 2

The new Composition has been generated, dependencies downloaded, and the schema updated. The next step is to define the Managed Resource.

Update the 01-compose.yaml.gotmpl file to define the ResourceGroup. The code: and yaml-language-server comments that start the file are required to support schema validation in VSCode.

 

# code: language=yaml
# yaml-language-server: $schema=../../.up/json/models/index.schema.json
{{ $parameters := $xr.spec.parameters }}
---
apiVersion: azure.upbound.io/v1beta1
kind: ResourceGroup
metadata:
  annotations:
    {{ setResourceNameAnnotation (print $parameters.id "-rg") }}
  labels:
    azure.platform.upbound.io/network-id: {{ $parameters.id }}
  name: {{ (print $parameters.id  "-rg") }}
spec:
  deletionPolicy: {{ $parameters.deletionPolicy }}
  forProvider:
    location: {{ $parameters.region }}
  providerConfigRef:
    name

Render the Composition using up composition render to make sure everything is working.

$ up composition render apis/xnetworks/composition-go-templating.yaml examples/network-xr.yaml
  ✓   Checking dependencies
  ✓   Generating language schemas
  ✓   Building functions
  ✓   Building configuration package
  ✓   Pushing embedded functions to local daemon
  ✓   Rendering
---
apiVersion: azure.platform.upbound.io/v1alpha1
kind: XNetwork
metadata:
  name: ref-azure-network
status:
  conditions:
  - lastTransitionTime: "2024-01-01T00:00:00Z"
    message: 'Unready resources: ref-azure-network-from-xr-rg'
    reason: Creating
    status: "False"
    type: Ready
---
apiVersion: azure.upbound.io/v1beta1
kind: ResourceGroup
metadata:
  annotations:
    crossplane.io/composition-resource-name: ref-azure-network-from-xr-rg
  generateName: ref-azure-network-
  labels:
    azure.platform.upbound.io/network-id: ref-azure-network-from-xr
    crossplane.io/composite: ref-azure-network
  name: ref-azure-network-from-xr-rg
  ownerReferences:
  - apiVersion: azure.platform.upbound.io/v1alpha1
    blockOwnerDeletion: true
    controller: true
    kind: XNetwork
    name: ref-azure-network
    uid: ""

Completing the Composition

Now that we know the Composition Pipeline works, we can complete the Embedded Function. Compare the version below to the original KCL version:

# code: language=yaml
# yaml-language-server: $schema=../../.up/json/models/index.schema.json
{{ $parameters := $xr.spec.parameters }}

{{- define "defaultSpec" -}}
deletionPolicy: {{ .deletionPolicy }}
managementPolicies: [ "*" ]
providerConfigRef:
  name: {{ .providerConfigName }}
{{- end }}

---
apiVersion: azure.upbound.io/v1beta1
kind: ResourceGroup
metadata:
  annotations:
    {{ setResourceNameAnnotation (print $parameters.id "-rg") }}
  labels:
    azure.platform.upbound.io/network-id: {{ $parameters.id }}
  name: {{ (print $parameters.id  "-rg") }}
spec:
  {{- include "defaultSpec" $parameters | nindent 2 }}
  forProvider:
    location: {{ $parameters.region }}
---
apiVersion: network.azure.upbound.io/v1beta2
kind: "VirtualNetwork"
metadata:
  annotations:
    {{ setResourceNameAnnotation (print $parameters.id "-vnet") }}
  labels:
    azure.platform.upbound.io/network-id: {{ $parameters.id }}
  name: {{ (print $parameters.id  "-vnet") }}
spec:
  {{- include "defaultSpec" $parameters | nindent 2 }}
  forProvider:
    location: {{ $parameters.region }}
    addressSpace: [ {{ $parameters.addressRange }} ]
    resourceGroupNameSelector:
      matchControllerRef: True
---                
apiVersion: network.azure.upbound.io/v1beta2
kind: "Subnet"
metadata:
  annotations:
    {{ setResourceNameAnnotation (print $parameters.id "-sn") }}
  labels:
    azure.platform.upbound.io/network-id: {{ $parameters.id }}
  name: {{ (print $parameters.id  "-sn") }}
spec:
  {{- include "defaultSpec" $parameters | nindent 2 }}
  forProvider:
    location: {{ $parameters.region }}
    addressPrefixes:  [ {{ $parameters.generalSubnetRange }} ]
    resourceGroupNameSelector:
      matchControllerRef: True
    serviceEndpoints: ["Microsoft.Sql"]
    virtualNetworkNameSelector:
      matchControllerRef: true               
{{- range $index, $subnet := $parameters.databaseSubnets }}
---                
apiVersion: network.azure.upbound.io/v1beta2
kind: "Subnet"
metadata:
  annotations:
    {{ setResourceNameAnnotation (print $parameters.id "-db-sn-" $index) }}
  labels:
    azure.platform.upbound.io/network-id: {{ $parameters.id }}
    azure.platform.upbound.io/subnet-service-type: {{ $subnet.serviceType }}
  name: {{ (print $parameters.id  "-db-sn-" $index) }}
spec:
  {{- include "defaultSpec" $parameters | nindent 2 }}
  forProvider:
    location: {{ $parameters.region }}
    addressPrefixes:  [ {{ $subnet.addressRange }} ]
    delegation: 
    - name: "fs"
      serviceDelegation:
        actions: ["Microsoft.Network/virtualNetworks/subnets/join/action"] 
        {{- if eq $subnet.serviceType "postgresql"}}
        name:  "Microsoft.DBforPostgreSQL/flexibleServers"
        {{- else }}
        name: "Microsoft.DBforMySQL/flexibleServers"
        {{- end }}
    resourceGroupNameSelector:
      matchControllerRef: True
    serviceEndpoints: ["Microsoft.Storage"]
    virtualNetworkNameSelector:
      matchControllerRef: true

Testing the Composition

Testing is a critical part when developing an Infrastructure Platform. Now that the Composition has been rewritten in Go Templating, it needs to be validated.

Control Plane Project tests support two ways of testing: Composition Tests run locally and ensure that the rendered manifests match a desired output, while End-to-End tests provision infrastructure in a cloud provider.

configuration-azure-network includes a comprehensive Composition Test at tests/test-xnetwork/main.k written in KCL. Since Control Plane Projects support mixing languages between Functions and Tests, we can reuse the existing test suites.

There are 4 separate tests in the test main.k file. Change the compositionPath for each of the 4 tests to point to the new Go Template Composition, from: 

compositionPath = "apis/xnetworks/composition.yaml"

to:

compositionPath = "apis/xnetworks/composition-go-templating.yaml"

Run the test using up test run and fix any errors that are reported until all four tests pass:

$ up test run tests/test-xnetwork
  ✓   Parsing tests                                                                                           
  ✓   Checking dependencies
  ✓   Generating language schemas
  ✓   Building functions
  ✓   Building configuration package
  ✓   Pushing embedded functions to local daemon
  ✓   Assert test-xnetwork-with-postgresql-db
  ✓   Assert test-xnetwork-with-mysql-db
  ✓   Assert test-xnetwork-without-db
  ✓   Assert test-xnetwork-with-multiple-dbs
SUCCESS: 
SUCCESS: Tests Summary:
SUCCESS: ------------------
SUCCESS: Total Tests Executed: 4
SUCCESS: Passed tests:         4
SUCCESS: Failed tests:         0

Success! The outputs of the Go Template match the Composition test.

Conclusion

With the addition of Go Template support, engineers who are familiar with Helm and other text templating languages can now manage infrastructure using Upbound's Developer tooling and Crossplane. Schema validation, rendering and testing enables Platform Engineers to refactor infrastructure code with confidence.

Get started with Upbound today using the quickstart.

About Authors

Steven Borrelli

Subscribe to the
Upbound Newsletter

Subscribe to the
Upbound Newsletter

Subscribe to the
Upbound Newsletter

Related

Related

Posts

Posts

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Nov 7, 2025

What Do Ice Cream and Crossplane Have in Common?

Ana Margarita Medina

Nov 7, 2025

What Do Ice Cream and Crossplane Have in Common?

Ana Margarita Medina

Nov 7, 2025

What Do Ice Cream and Crossplane Have in Common?

Ana Margarita Medina

Nov 6, 2025

From Declarative to Intelligent: How Crossplane’s Graduation Redefines Infrastructure

Bassam Tabbara

Founder and CEO

Nov 6, 2025

From Declarative to Intelligent: How Crossplane’s Graduation Redefines Infrastructure

Bassam Tabbara

Founder and CEO

Nov 6, 2025

From Declarative to Intelligent: How Crossplane’s Graduation Redefines Infrastructure

Bassam Tabbara

Founder and CEO

Nov 6, 2025

Crossplane Graduates From CNCF as Upbound Redefines the Future of AI-Native Infrastructure

Upbound

Nov 6, 2025

Crossplane Graduates From CNCF as Upbound Redefines the Future of AI-Native Infrastructure

Upbound

Nov 6, 2025

Crossplane Graduates From CNCF as Upbound Redefines the Future of AI-Native Infrastructure

Upbound

Get Started with Upbound Crossplane 2.0

Trusted by 1,000+ organizations and downloaded over 100 million times.

Get Started with Upbound Crossplane 2.0

Trusted by 1,000+ organizations and downloaded over 100 million times.

Get Started with Upbound Crossplane 2.0

Trusted by 1,000+ organizations and downloaded over 100 million times.

The Platform Cloud™

This should be crafted with love by our globally distributed team.

Upbound is an active contributor to Crossplane and the Cloud Native Computing Foundation

The Platform Cloud™

This should be crafted with love by our globally distributed team.

Upbound is an active contributor to Crossplane and the Cloud Native Computing Foundation

The Platform Cloud™

This should be crafted with love by our globally distributed team.

Upbound is an active contributor to Crossplane and the Cloud Native Computing Foundation