Skip to content

Latest commit

 

History

History
178 lines (141 loc) · 4.73 KB

File metadata and controls

178 lines (141 loc) · 4.73 KB

Interfaces

The SDK provides two interfaces for implementing KRM functions. Choose according to your function requirements.

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 functionConfig into your struct's exported fields (via JSON tags).
  • You can modify existing items, but you cannot add or remove items from the slice.
  • Return true for success, false for failure.
  • Use results to 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 functionConfig manually from rl.FunctionConfig.
  • You can modify rl.Results directly.
  • 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

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:

  1. Parsing functionConfig into your struct fields
  2. Calling your Run method with the parsed context
  3. Collecting results and determining success/failure

Next: Testing — golden test patterns for verifying your function.