Interfaces
The SDK provides two interfaces for implementing KRM functions. Choose according to your function requirements.
The
mainfunctions below callfn.AsMainwithoutfn.WithDocsto keep the interface examples focused. Production functions should embed documentation and passfn.WithDocs(readme, metadata)— see the tutorial.
fn.Runner #
Use fn.Runner for transformers (mutators) and validators. This is the
recommended interface for most functions.
type Runner interface {
Run(context *Context, functionConfig *KubeObject, items KubeObjects, results *Results) bool
}
Characteristics:
- The SDK automatically parses
functionConfiginto your struct’s exported fields. A typed functionConfig (itskindmatching your struct name) is unmarshaled via JSON tags; alternatively, aConfigMapfunctionConfig has its.datamap assigned to amap[string]stringfield on your struct. - You can modify existing items, but adding or removing items is not supported.
This is a convention, not a compile-time restriction: the SDK does not read back
items appended inside
Run, so adds and removes are effectively dropped. Usefn.ResourceListProcessorwhen you need to add or remove items. - Return
truefor success,falsefor failure. - Use
resultsto report structured info/warning/error messages.
Example: Validator #
var _ fn.Runner = &EnforceNamespace{}
type EnforceNamespace struct {
Namespace string `json:"namespace"`
}
func (r *EnforceNamespace) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool {
for _, obj := range items {
if obj.GetNamespace() != r.Namespace {
results.Errorf("resource %s/%s has namespace %q, expected %q",
obj.GetKind(), obj.GetName(), obj.GetNamespace(), r.Namespace)
}
}
return results.ExitCode() == 0
}
func main() {
runner := fn.WithContext(context.Background(), &EnforceNamespace{})
if err := fn.AsMain(runner); err != nil {
os.Exit(1)
}
}
Example: Transformer (Mutator) #
var _ fn.Runner = &SetAnnotations{}
type SetAnnotations struct {
Annotations map[string]string `json:"annotations,omitempty"`
}
func (r *SetAnnotations) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool {
for _, obj := range items {
for k, v := range r.Annotations {
if err := obj.SetAnnotation(k, v); err != nil {
results.ErrorE(err)
}
}
}
return results.ExitCode() == 0
}
fn.ResourceListProcessor #
Use fn.ResourceListProcessor for generators and complex functions that
need full control over the ResourceList.
type ResourceListProcessor interface {
Process(rl *ResourceList) (bool, error)
}
Characteristics:
- Full access to
ResourceList.Items— you can add, remove, or modify items. - You must parse
functionConfigmanually fromrl.FunctionConfig. - You can modify
rl.Resultsdirectly. - Return
(true, nil)for success,(false, err)for failure.
Example: Generator #
type ConfigMapGenerator struct{}
func (g *ConfigMapGenerator) Process(rl *fn.ResourceList) (bool, error) {
// Parse functionConfig manually
name, _, _ := rl.FunctionConfig.NestedString("metadata", "name")
// Generate a new ConfigMap
cm := fn.NewEmptyKubeObject()
if err := cm.SetAPIVersion("v1"); err != nil {
return false, err
}
if err := cm.SetKind("ConfigMap"); err != nil {
return false, err
}
if err := cm.SetName(name + "-generated"); err != nil {
return false, err
}
if err := cm.SetNamespace("default"); err != nil {
return false, err
}
// Add to items
rl.Items = append(rl.Items, cm)
return true, nil
}
func main() {
if err := fn.AsMain(&ConfigMapGenerator{}); err != nil {
os.Exit(1)
}
}
ResourceListProcessorFunc #
For simple cases, use the function adapter instead of defining a struct:
type ResourceListProcessorFunc func(rl *ResourceList) (bool, error)
Example:
func main() {
processor := fn.ResourceListProcessorFunc(func(rl *fn.ResourceList) (bool, error) {
for _, obj := range rl.Items {
if err := obj.SetLabel("managed-by", "my-function"); err != nil {
return false, err
}
}
return true, nil
})
if err := fn.AsMain(processor); err != nil {
os.Exit(1)
}
}
Choosing Between Interfaces #
| Capability | fn.Runner | fn.ResourceListProcessor |
|---|---|---|
| Auto-parse functionConfig | ✅ | ❌ (manual) |
| Modify existing items | ✅ | ✅ |
| Add new items | ❌ | ✅ |
| Remove items | ❌ | ✅ |
| Access full ResourceList | ❌ | ✅ |
| Best for | Transformers, Validators | Generators, Complex functions |
As a rule of thumb, pick the interface by what your function does:
- Transformers and validators — use
fn.Runner. It auto-parses the functionConfig and keeps the function focused on modifying items. Examples: set-labels, set-namespace. - Generators and functions needing full ResourceList access (adding or
removing items, reading results from earlier functions) — use
fn.ResourceListProcessor. Examples: render-helm-chart, starlark.
Both produce spec-compliant ResourceList I/O; the choice is about ergonomics, so use the one that fits your function rather than a hard requirement.
Wrapping a Runner #
fn.Runner is wrapped into a ResourceListProcessor internally using
fn.WithContext:
runner := fn.WithContext(context.Background(), &MyFunction{})
// runner implements ResourceListProcessor and can be passed to fn.AsMain
This wrapper handles the following:
- Parsing
functionConfiginto your struct fields - Calling your
Runmethod with the parsed context - Collecting results and determining success/failure
Next: Testing — golden test patterns for verifying your function.