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: {{ $parameters.providerConfigName }}
In this Blog we'll port an existing Composition to a Go Template, covering dependencies, schema generation, rendering and testing.
You'll need a minimum version v0.39.0 of Upbound's up binary. Run up version to confirm
you have the binary installed and at the proper release:
$ up version
Client:
Version: v0.39.0
Go Version: go1.23.8
Git Commit: unknown-commit
OS/Arch: darwin/arm64
Server:
Crossplane Version: v1.19.0-up.1
Spaces Controller Version: Upbound Cloud ManagedYou'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.
To enable the best developer experience with Go Templates, several VSCode Extensions should be installed:
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:
apisWhere Platform APIs are definedexamplesExample manifests for documentation and testingfunctionsEmbedded Composition Functions where resources are definedtestsComposition and End-to-End (e2e) Testsupbound.yamla 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 configuration-azure-networkAn 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 go-templating
successfully created Composition and saved to /home/platform/code/configuration-azure-network/apis/xnetworks/composition-go-templating.yamlThe 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 filesNow 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 Composition
successfully created Function and saved to /home/platform/code/configuration-azure-network/functions/xnetwork-go-templatingThis 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 filesThe .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: xnetwork-go-templatingTo 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 project build
up project build
✓ Checking dependencies
✓ Generating language schemas
✓ Building functions
✓ Building configuration package
✓ Writing packages to _output/configuration-azure-network.uppkgA 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 dep add xpkg.upbound.io/upbound/provider-family-azure:v1
✓ Updating cache dependencies...
SUCCESS xpkg.upbound.io/upbound/provider-family-azure:v1 added to cache
✓ Updating project dependencies...
SUCCESS xpkg.upbound.io/upbound/provider-family-azure:v1 added to project dependencyGo Templates use the models stored in the json directory.
$ tree -L 2 .up
.up
├── go
│ └── models
├── json
│ └── models
├── kcl
│ └── models
└── python
└── modelsThe 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: {{ $parameters.providerConfigName }}
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: ""
spec:
deletionPolicy: Delete
forProvider:
location: westus
providerConfigRef:
name: defaultNow 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
{{- end }}
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: 0Success! The outputs of the Go Template match the Composition test.
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.