Skip to content

Commit df6c9ee

Browse files
Your Nameabdulrahman11a
authored andcommitted
feat(builtins): built-in runtime for apply-replacements and starlark (#4307)
Closes #4307 Implements a built-in runtime for curated KRM functions inside kpt, allowing apply-replacements and starlark to run without pulling images from Docker Hub, eliminating the external SDK dependency. Avoids circular dependency between Porch and kpt by moving the built-in runtime concept into kpt directly using kyaml/fn/framework instead of krm-functions-sdk. A thread-safe self-registration registry (internal/builtins/registry) allows KRM function implementations to register themselves via init(). The fnruntime runner checks this registry before falling back to Docker or WASM, preserving existing behavior for unregistered functions. Priority order in fnruntime/runner.go: 1. pkg-context builtin (existing) 2. Builtin registry (new, no Docker needed) 3. Docker / WASM (fallback, unchanged) - apply-replacements: ghcr.io/kptdev/krm-functions-catalog/apply-replacements - starlark: ghcr.io/kptdev/krm-functions-catalog/starlark - Removed dependency on krm-functions-sdk/go/fn and krm-functions-catalog/starlark - Vendored starlark runtime locally using kyaml/yaml instead of SDK [PASS] apply-replacements in 0s (no Docker) [PASS] starlark in 0s (no Docker) Signed-off-by: abdulrahman11a <abdulrahmanfikry1@gmail.com>
1 parent 390ab84 commit df6c9ee

File tree

6 files changed

+38
-39
lines changed

6 files changed

+38
-39
lines changed

internal/builtins/registry/registry.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func Lookup(imageName string) BuiltinFunction {
4848
}
4949
fn := registry[normalized]
5050
if fn != nil && imageName != normalized {
51-
klog.Warningf("WARNING: builtin function %q is being used instead of the requested image %q. "+
51+
klog.V(4).Infof("builtin function %q is being used instead of the requested image %q. "+
5252
"The built-in implementation may differ from the pinned version.", normalized, imageName)
5353
}
5454
return fn

internal/builtins/starlark/processor_test.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,8 @@ import (
2121
"github.com/stretchr/testify/assert"
2222
"sigs.k8s.io/kustomize/kyaml/fn/framework"
2323
"sigs.k8s.io/kustomize/kyaml/kio"
24-
"sigs.k8s.io/kustomize/kyaml/yaml"
2524
)
2625

27-
func parseResourceList(t *testing.T, input string) *framework.ResourceList {
28-
t.Helper()
29-
rw := &kio.ByteReader{Reader: strings.NewReader(input)}
30-
nodes, err := rw.Read()
31-
assert.NoError(t, err)
32-
33-
rl := &framework.ResourceList{Items: nodes}
34-
node, err := yaml.Parse(input)
35-
if err == nil {
36-
fc := node.Field("functionConfig")
37-
if fc != nil && fc.Value != nil {
38-
rl.FunctionConfig = fc.Value
39-
}
40-
}
41-
return rl
42-
}
43-
4426
func TestProcess_SetNamespace(t *testing.T) {
4527
input := `apiVersion: config.kubernetes.io/v1
4628
kind: ResourceList

internal/builtins/starlark/runtime/context.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,24 @@ func oa() (starlark.Value, error) {
4141
return interfaceToValue(openapi.Schema())
4242
}
4343

44+
// allowlist of safe environment variables to expose to starlark scripts
45+
var allowedEnvVars = map[string]bool{
46+
"HOME": true,
47+
"PATH": true,
48+
"USER": true,
49+
"HOSTNAME": true,
50+
}
51+
4452
func env() (starlark.Value, error) {
4553
env := map[string]interface{}{}
4654
for _, e := range os.Environ() {
4755
pair := strings.SplitN(e, "=", 2)
4856
if len(pair) < 2 {
4957
continue
5058
}
51-
env[pair[0]] = pair[1]
59+
if allowedEnvVars[pair[0]] {
60+
env[pair[0]] = pair[1]
61+
}
5262
}
5363
value, err := util.Marshal(env)
5464
if err != nil {

internal/builtins/starlark/runtime/doc.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,22 @@
99
// Examples: https://github.com/cruise-automation/isopod, https://qri.io/docs/starlark/starlib,
1010
// https://github.com/stripe/skycfg, https://github.com/k14s/ytt
1111
//
12-
// The resources are provided to the starlark program through the global variable "resourceList".
13-
// "resourceList" is a dictionary containing an "items" field with a list of resources.
14-
// The starlark modified "resourceList" is the Filter output.
12+
// The resources are provided to the starlark program through ctx.resource_list.
13+
// ctx.resource_list is a dictionary containing an "items" field with a list of resources.
14+
// The starlark-modified ctx.resource_list is the Filter output.
1515
//
16-
// After being run through the starlark program, the filter will copy the comments from the input
17-
// resources to restore them -- due to them being dropped as a result of serializing the resources
18-
// as starlark values.
19-
//
20-
// "resourceList" may also contain a "functionConfig" entry to configure the starlark script itself.
21-
// Changes made by the starlark program to the "functionConfig" will be reflected in the
16+
// ctx.resource_list may also contain a "functionConfig" entry to configure the starlark script
17+
// itself. Changes made by the starlark program to the "functionConfig" will be reflected in the
2218
// Filter.FunctionConfig value.
2319
//
2420
// The Filter will also format the output so that output has the preferred field ordering
2521
// rather than an alphabetical field ordering.
2622
//
27-
// The resourceList variable adheres to the kustomize function spec as specified by:
23+
// The ctx.resource_list value adheres to the kustomize function spec as specified by:
2824
// https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md
2925
//
30-
// All items in the resourceList are resources represented as starlark dictionaries/
31-
// The items in the resourceList respect the io spec specified by:
26+
// All items in ctx.resource_list are resources represented as starlark dictionaries/
27+
// The items in ctx.resource_list respect the io spec specified by:
3228
// https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/config-io.md
3329
//
3430
// The starlark language spec can be found here:

internal/builtins/starlark/runtime/starlark.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"net/http"
1111
"os"
12+
"time"
1213

1314
"github.com/qri-io/starlib/util"
1415
"go.starlark.net/resolve"
@@ -58,7 +59,6 @@ func (sf *Filter) setup() error {
5859
return errors.Errorf("Filter Path, Program and URL are mutually exclusive")
5960
}
6061

61-
// read the program from a file
6262
if sf.Path != "" {
6363
b, err := os.ReadFile(sf.Path)
6464
if err != nil {
@@ -67,18 +67,20 @@ func (sf *Filter) setup() error {
6767
sf.Program = string(b)
6868
}
6969

70-
// read the program from a URL
7170
if sf.URL != "" {
7271
err := func() error {
73-
resp, err := http.Get(sf.URL)
72+
client := &http.Client{Timeout: 30 * time.Second}
73+
resp, err := client.Get(sf.URL)
7474
if err != nil {
7575
return err
7676
}
77-
7877
defer func() {
7978
_ = resp.Body.Close()
8079
}()
81-
80+
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
81+
return fmt.Errorf("failed to fetch starlark program from %s: HTTP %d",
82+
sf.URL, resp.StatusCode)
83+
}
8284
b, err := io.ReadAll(resp.Body)
8385
if err != nil {
8486
return err

internal/fnruntime/runner.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func NewRunner(
8686
} else if runner != nil {
8787
fltr.Run = runner.Run
8888
}
89+
8990
}
9091
if fltr.Run == nil {
9192
if f.Image == runneroptions.FuncGenPkgContext {
@@ -233,7 +234,7 @@ func (fr *FunctionRunner) Filter(input []*yaml.RNode) (output []*yaml.RNode, err
233234
return nil, errors.ErrAlreadyHandled
234235
}
235236
// for builtin functions, print stderr from fnResult if available
236-
if fr.fnResult.Stderr != "" {
237+
if err != nil && fr.fnResult.Stderr != "" {
237238
printFnStderr(fr.ctx, fr.fnResult.Stderr)
238239
pr.Printf(" Exit code: %d\n\n", fr.fnResult.ExitCode)
239240
return nil, errors.ErrAlreadyHandled
@@ -295,7 +296,15 @@ func (fr *FunctionRunner) do(input []*yaml.RNode) (output []*yaml.RNode, err err
295296
fnResult.Stderr = execErr.Stderr
296297
} else {
297298
// builtin functions don't return ExecError, populate stderr from error message
298-
fnResult.Stderr = err.Error()
299+
if fnResult.Stderr == "" {
300+
fnResult.Stderr = err.Error()
301+
} else if !strings.Contains(fnResult.Stderr, err.Error()) {
302+
if strings.HasSuffix(fnResult.Stderr, "\n") {
303+
fnResult.Stderr += err.Error()
304+
} else {
305+
fnResult.Stderr += "\n" + err.Error()
306+
}
307+
}
299308
}
300309
// accumulate the results
301310
fr.fnResults.Items = append(fr.fnResults.Items, *fnResult)

0 commit comments

Comments
 (0)