Formalize BuiltinFunction to an interface and use it throughout
Introduce BuiltinFunction interface for builtin step functions
Motivation
Builtin step functions are currently registered as three loose pieces — a name, a proto.Spec, and a StepFunc — that have to be passed around together everywhere lookup and registration happen. There's nothing tying them to a single value, so the same triple gets re-shuffled in pkg/di, pkg/runner/function, pkg/runner/resource, pkg/testutil, and every builtin's tests. It's also easy to forget which proto.Spec matches which StepFunc when registering aliases (the function/oci/
This MR introduces a BuiltinFunction interface that bundles all three, plumbs it end-to-end through registration and lookup, and converts each of the four default builtins to implement it.
What changes
New interface
- runner.BuiltinFunction — interface with Name() string, Spec() *proto.Spec, Run(ctx, BuiltinContext) error.
- runner.NewBuiltinFunc(name, spec, run) — adapter that wraps a (name, spec, run) triple as a BuiltinFunction. Used internally to bridge the deprecated triple-based APIs and as a convenience for ad-hoc test finders.
Default builtins now implement the interface
Each of dist/steps/script, dist/steps/docker/auth, dist/steps/step/oci/build, and dist/steps/step/oci/promote now exposes a Function type and a New() constructor that returns a BuiltinFunction:
script.New() // canonical name: "script" auth.New() // canonical name: "docker/auth" build.New() // canonical name: "function/oci/build" promote.New() // canonical name: "function/oci/publish"
The existing step/oci/build and step/oci/promote names remain registered as deprecated aliases, now via the new di.WithAlias option so the alias relationship is explicit at the registration site (rather than two anonymous WithStepFunc calls registering the same impl twice).
Plumbing flows BuiltinFunction end-to-end
- runner.StepFuncFinder returns (BuiltinFunction, error) instead of (*proto.Spec, StepFunc, error). Consumers (pkg/runner/function, pkg/runner/resource) pull .Spec()/.Run off the returned value.
- StepFunctionRepository stores map[string]BuiltinFunction. RegisterBuiltin(name, b) takes the registration name explicitly, decoupled from b.Name(), so aliases don't need a synthetic wrapper.
- di.Container stores map[string]BuiltinFunction. WithBuiltinFunc(b) keys by b.Name(); WithAlias(name, b) keys by the explicit name.
- testutil.StepRunnerBuilder.RegisterBuiltin(b) replaces the old RegisterStepFunc(name, spec, fn).
Backwards compatibility
Nothing currently exported is removed. The following are kept defined for external callers but marked Deprecated::
- runner.StepFunc
- runner.StepFunctionRepository.Register(step, spec, run)
- di.WithStepFunc(name, spec, fn)
All three have zero internal callers after this change. make go-lint reports 0 issues without any //nolint:staticcheck suppressions.
Verification
- go build ./... clean
- make go-lint — 0 issues
- make test — passes locally
- Integration test TestCustomFunctionRunsAsStep (which exercises testutil.RegisterBuiltin) passes
Follow-ups (out of scope here)
- Remove the deprecated StepFunc, Register(step, spec, run), and WithStepFunc once external callers have migrated. That removal also lets us drop the NewBuiltinFunc adapter — its only remaining users are these deprecated APIs and a handful of test finder closures.
- Possible rename of RegisterBuiltin → Register once the deprecated Register is gone.
- Update the 2 builtins in the runner repo to implement this new interface.