The SDK provides two interfaces for implementing KRM functions. Choose according to your function requirements.
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 (via JSON tags). - You can modify existing items, but you cannot add or remove items from the slice.
- Return
truefor success,falsefor failure. - Use
resultsto report structured info/warning/error messages.
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)
}
}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
}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.
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)
}
}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)
}
}| 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 |
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.AsMainThis 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.