This tutorial walks through the end-to-end workflow for building a KRM function
using the Go SDK. By the end, you will have a working function with embedded
documentation, golden tests, and support for --help, --doc, and standalone
file mode.
For a complete working example, see go/get-started/.
A KRM function implements the fn.Runner interface:
type Runner interface {
Run(context *Context, functionConfig *KubeObject, items KubeObjects, results *Results) bool
}Here is a minimal function that sets labels on all the resources:
package main
import (
"context"
_ "embed"
"os"
"github.com/kptdev/krm-functions-sdk/go/fn"
)
//go:embed README.md
var readme []byte
//go:embed metadata.yaml
var metadata []byte
var _ fn.Runner = &SetLabels{}
type SetLabels struct {
Labels map[string]string `json:"labels,omitempty"`
}
func (r *SetLabels) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool {
for _, obj := range items {
for k, v := range r.Labels {
if err := obj.SetLabel(k, v); err != nil {
results.ErrorE(err)
}
}
}
return results.ExitCode() == 0
}
func main() {
runner := fn.WithContext(context.Background(), &SetLabels{})
if err := fn.AsMain(runner, fn.WithDocs(readme, metadata)); err != nil {
os.Exit(1)
}
}Key points:
- Your struct fields are automatically populated from
functionConfig(JSON unmarshaling). - Return
truefor success,falsefor failure. - Use
resultsto report structured messages (info, warning, error).
The SDK uses Go's embed directive to bundle documentation into the binary. Two files are needed:
Use <!--mdtogo--> markers to define sections that --help and --doc extract:
# set-labels
<!--mdtogo:Short-->
Set labels on all resources in the package.
<!--mdtogo-->
<!--mdtogo:Long-->
## Usage
The `set-labels` function adds or updates labels on all KRM resources.
It accepts a `SetLabels` functionConfig with a `labels` map.
### FunctionConfig
```yaml
apiVersion: fn.kpt.dev/v1alpha1
kind: SetLabels
metadata:
name: my-config
labels:
app: my-app
env: production
```
<!--mdtogo-->
<!--mdtogo:Examples-->
Set a single label on all resources:
```yaml
apiVersion: fn.kpt.dev/v1alpha1
kind: SetLabels
labels:
team: platform
```
<!--mdtogo-->
image: ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1
description: Set labels on all resources
tags:
- mutator
- labels
sourceURL: https://github.com/kptdev/krm-functions/tree/main/functions/go/set-labels
examplePackageURLs:
- https://github.com/kptdev/krm-functions/tree/main/examples/set-labels-simple
license: Apache-2.0
hidden: falseIn your main.go:
//go:embed README.md
var readme []byte
//go:embed metadata.yaml
var metadata []byte
func main() {
runner := fn.WithContext(context.Background(), &SetLabels{})
if err := fn.AsMain(runner, fn.WithDocs(readme, metadata)); err != nil {
os.Exit(1)
}
}Pipe a ResourceList through your function:
cat input.yaml | go run . > output.yamlView human-readable documentation:
go run . --helpThis prints the Short, Long, and Examples sections extracted from your README markers.
Get machine-readable JSON documentation (consumed by kpt fn doc and catalog pipelines):
go run . --docProcess KRM files directly without constructing a ResourceList:
go run . deployment.yaml service.yamlThis reads the YAML files, assembles them into a ResourceList with an empty functionConfig, processes them, and writes the result to STDOUT.
The SDK provides testhelpers.RunGoldenTests for snapshot-based testing.
Create a test directory structure:
testdata/
├── test-case-1/
│ ├── _expected.yaml # Expected output (ResourceList YAML)
│ ├── _fnconfig.yaml # FunctionConfig for this test case
│ └── resources.yaml # Input resources
└── test-case-2/
├── _expected.yaml
├── _fnconfig.yaml
└── resources.yaml
Write your test:
func TestFunction(t *testing.T) {
runner := fn.WithContext(context.TODO(), &SetLabels{})
testhelpers.RunGoldenTests(t, "testdata", runner)
}Update expected output after changes:
WRITE_GOLDEN_OUTPUT=1 go test ./...See testing for more details.
- Interfaces — when to use
fn.Runnervsfn.ResourceListProcessor - Testing — golden test patterns in depth
- Containerizing — packaging your function as a container image