pkg/function/main.go (102 lines of code) (raw):

package function import ( "fmt" "reflect" krmv1 "github.com/Azure/eno/pkg/krm/functions/api/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) // Inputs is satisfied by any struct that defines the inputs required by a SynthFunc. // Use the `eno_key` struct tag to specify the corresponding ref key for each input. // Each field must either be a client.Object or a custom type registered with AddCustomInputType. type Inputs interface{} // SynthFunc defines a synthesizer function that takes a set of inputs and returns a list of objects. type SynthFunc[T Inputs] func(inputs T) ([]client.Object, error) // Main is the entrypoint for Eno synthesizer processes written using the framework defined by this package. func Main[T Inputs](fn SynthFunc[T]) { ow := NewDefaultOutputWriter() ir, err := NewDefaultInputReader() if err != nil { panic(fmt.Sprintf("failed to create default input reader: %s", err)) } err = main(fn, ir, ow) if err != nil { panic(fmt.Sprintf("error while calling synthesizer function: %s", err)) } } func main[T Inputs](fn SynthFunc[T], ir *InputReader, ow *OutputWriter) error { var inputs T v := reflect.ValueOf(&inputs).Elem() t := v.Type() // Read the inputs for i := 0; i < t.NumField(); i++ { tagValue := t.Field(i).Tag.Get("eno_key") if tagValue == "" { continue } input, err := newInput(ir, v.Field(i)) if err != nil { return err } err = ReadInput(ir, tagValue, input.Object) if err != nil { ow.AddResult(&krmv1.Result{ Message: fmt.Sprintf("error while reading input with key %q: %s", tagValue, err), Severity: krmv1.ResultSeverityError, }) return ow.Write() } input.Finalize() } // Call the fn and handle errors through the KRM interface outputs, err := fn(inputs) if err != nil { ow.AddResult(&krmv1.Result{ Message: err.Error(), Severity: krmv1.ResultSeverityError, }) return ow.Write() } // Write the outputs for _, out := range outputs { ow.Add(out) } return ow.Write() } var customInputSourceTypes = map[string]reflect.Type{} var customInputBindings = map[string]func(any) (any, error){} // AddCustomInputType allows types that do not implement client.Object to be used as fields of Inputs structs. func AddCustomInputType[Resource client.Object, Custom any](bind func(Resource) (Custom, error)) { str := reflect.TypeOf(bind).Out(0).String() // Map from custom type name to the underlying k8s input type var res Resource customInputSourceTypes[str] = reflect.TypeOf(res) // Map from the custom type name to the binding function customInputBindings[str] = func(in any) (any, error) { return bind(in.(Resource)) } } type input struct { Object client.Object bindFn func(any) (any, error) field reflect.Value } func newInput(ir *InputReader, field reflect.Value) (*input, error) { i := &input{field: field} // Allocate values for nil pointers if field.IsNil() { field.Set(reflect.New(field.Type().Elem())) } // Pass through client.Object types fieldVal := field.Interface() if o, ok := fieldVal.(client.Object); ok { i.Object = o return i, nil } // Resolve custom input types back to their binding functions name := field.Type().String() inputSourceType, ok := customInputSourceTypes[name] if !ok { return nil, fmt.Errorf("custom input type %q has not been registered", name) } fieldVal = reflect.New(inputSourceType.Elem()).Interface() i.Object = fieldVal.(client.Object) i.bindFn = customInputBindings[name] return i, nil } func (i *input) Finalize() error { if i.bindFn == nil { return nil } bound, err := i.bindFn(i.Object) if err != nil { return fmt.Errorf("error while binding custom input of type %T: %s", i.Object, err) } i.field.Set(reflect.ValueOf(bound)) return nil }