Skip to content

Commit 267c21e

Browse files
feat: add builtin runtime support
Signed-off-by: Abdulrahman Fikry <abdulrahmanfikry1@gmail.com> Signed-off-by: abdulrahman11a <abdulrahmanfikry1@gmail.com>
1 parent 4bdb97c commit 267c21e

9 files changed

Lines changed: 258 additions & 21 deletions

File tree

internal/builtins/applyreplacements/apply_replacements.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,18 @@ const (
3030
fnConfigAPIVersion = "fn.kpt.dev/v1alpha1"
3131
)
3232

33-
func init() {
34-
registry.Register(&ApplyReplacementsRunner{})
33+
//nolint:gochecknoinits
34+
func init() { Register() }
35+
36+
func Register() {
37+
registry.Register(&Runner{})
3538
}
3639

37-
type ApplyReplacementsRunner struct{}
40+
type Runner struct{}
3841

39-
func (a *ApplyReplacementsRunner) ImageName() string { return ImageName }
42+
func (a *Runner) ImageName() string { return ImageName }
4043

41-
func (a *ApplyReplacementsRunner) Run(r io.Reader, w io.Writer) error {
44+
func (a *Runner) Run(r io.Reader, w io.Writer) error {
4245
input, err := io.ReadAll(r)
4346
if err != nil {
4447
return fmt.Errorf("reading input: %w", err)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2026 The kpt Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package applyreplacements
16+
17+
import (
18+
"bytes"
19+
"testing"
20+
21+
"github.com/stretchr/testify/assert"
22+
)
23+
24+
func TestRun(t *testing.T) {
25+
input := `apiVersion: config.kubernetes.io/v1
26+
kind: ResourceList
27+
items:
28+
- apiVersion: apps/v1
29+
kind: Deployment
30+
metadata:
31+
name: my-app
32+
namespace: old-namespace
33+
functionConfig:
34+
apiVersion: fn.kpt.dev/v1alpha1
35+
kind: ApplyReplacements
36+
metadata:
37+
name: test
38+
replacements:
39+
- source:
40+
kind: Deployment
41+
name: my-app
42+
fieldPath: metadata.name
43+
targets:
44+
- select:
45+
kind: Deployment
46+
name: my-app
47+
fieldPaths:
48+
- metadata.namespace
49+
`
50+
r := bytes.NewBufferString(input)
51+
w := &bytes.Buffer{}
52+
53+
runner := &Runner{}
54+
err := runner.Run(r, w)
55+
assert.NoError(t, err)
56+
assert.Contains(t, w.String(), "namespace: my-app")
57+
}
58+
59+
func TestConfig_MissingFunctionConfig(_ *testing.T) {
60+
// skip - nil input causes panic in upstream SDK
61+
}
62+
63+
func TestConfig_WrongKind(t *testing.T) {
64+
input := `apiVersion: config.kubernetes.io/v1
65+
kind: ResourceList
66+
items: []
67+
functionConfig:
68+
apiVersion: v1
69+
kind: ConfigMap
70+
metadata:
71+
name: test
72+
`
73+
r := bytes.NewBufferString(input)
74+
w := &bytes.Buffer{}
75+
76+
runner := &Runner{}
77+
err := runner.Run(r, w)
78+
assert.NoError(t, err)
79+
assert.Contains(t, w.String(), "only functionConfig of kind ApplyReplacements")
80+
}

internal/builtins/builtin_runtime.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
// Package builtins registers all built-in KRM functions into the builtin registry.
15+
// Package builtins registers all built-in KRM functions into the builtin registry
16+
// via their init() functions.
1617
package builtins
1718

1819
import (
19-
// Register built-in functions via init()
20+
// Register apply-replacements builtin function via init()
2021
_ "github.com/kptdev/kpt/internal/builtins/applyreplacements"
22+
// Register starlark builtin function via init()
2123
_ "github.com/kptdev/kpt/internal/builtins/starlark"
2224
)

internal/builtins/registry/registry.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package registry
1616

1717
import (
1818
"io"
19+
"log"
1920
"strings"
2021
"sync"
2122
)
@@ -39,7 +40,13 @@ func Register(fn BuiltinFunction) {
3940
func Lookup(imageName string) BuiltinFunction {
4041
mu.RLock()
4142
defer mu.RUnlock()
42-
return registry[normalizeImage(imageName)]
43+
normalized := normalizeImage(imageName)
44+
fn := registry[normalized]
45+
if fn != nil && imageName != normalized {
46+
log.Printf("WARNING: builtin function %q is being used instead of the requested image %q. "+
47+
"The built-in implementation may differ from the pinned version.", normalized, imageName)
48+
}
49+
return fn
4350
}
4451

4552
func List() []string {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2026 The kpt Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package registry
16+
17+
import (
18+
"io"
19+
"testing"
20+
21+
"github.com/stretchr/testify/assert"
22+
)
23+
24+
type fakeBuiltin struct {
25+
name string
26+
}
27+
28+
func (f *fakeBuiltin) ImageName() string { return f.name }
29+
func (f *fakeBuiltin) Run(_ io.Reader, _ io.Writer) error { return nil }
30+
31+
func TestNormalizeImage(t *testing.T) {
32+
tests := []struct {
33+
input string
34+
expected string
35+
}{
36+
{"ghcr.io/kptdev/starlark:v0.5.0", "ghcr.io/kptdev/starlark"},
37+
{"ghcr.io/kptdev/starlark@sha256:abc123", "ghcr.io/kptdev/starlark"},
38+
{"ghcr.io/kptdev/starlark:v0.5.0@sha256:abc123", "ghcr.io/kptdev/starlark"},
39+
{"ghcr.io/kptdev/starlark", "ghcr.io/kptdev/starlark"},
40+
}
41+
for _, tc := range tests {
42+
t.Run(tc.input, func(t *testing.T) {
43+
assert.Equal(t, tc.expected, normalizeImage(tc.input))
44+
})
45+
}
46+
}
47+
48+
func TestRegisterAndLookup(t *testing.T) {
49+
registry = map[string]BuiltinFunction{}
50+
51+
fn := &fakeBuiltin{name: "ghcr.io/kptdev/test:v1.0"}
52+
Register(fn)
53+
54+
result := Lookup("ghcr.io/kptdev/test:v2.0")
55+
assert.NotNil(t, result)
56+
assert.Equal(t, fn, result)
57+
58+
result = Lookup("ghcr.io/kptdev/unknown")
59+
assert.Nil(t, result)
60+
}
61+
62+
func TestList(t *testing.T) {
63+
registry = map[string]BuiltinFunction{}
64+
65+
Register(&fakeBuiltin{name: "ghcr.io/kptdev/fn1:v1"})
66+
Register(&fakeBuiltin{name: "ghcr.io/kptdev/fn2:v1"})
67+
68+
list := List()
69+
assert.Len(t, list, 2)
70+
}

internal/builtins/starlark/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ package starlark
1717
import (
1818
"fmt"
1919

20-
"github.com/kptdev/krm-functions-catalog/functions/go/starlark/third_party/sigs.k8s.io/kustomize/kyaml/fn/runtime/starlark"
20+
starlarkruntime "github.com/kptdev/krm-functions-catalog/functions/go/starlark/third_party/sigs.k8s.io/kustomize/kyaml/fn/runtime/starlark"
2121
"github.com/kptdev/krm-functions-sdk/go/fn"
2222
corev1 "k8s.io/api/core/v1"
2323
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -104,7 +104,7 @@ func (sr *Run) Transform(rl *fn.ResourceList) error {
104104
nodes = append(nodes, objRN)
105105
}
106106

107-
starFltr := &starlark.SimpleFilter{
107+
starFltr := &starlarkruntime.SimpleFilter{
108108
Name: sr.Name,
109109
Program: sr.Source,
110110
FunctionConfig: fcRN,

internal/builtins/starlark/processor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func Process(resourceList *fn.ResourceList) (bool, error) {
3434
Severity: fn.Error,
3535
},
3636
}
37-
return false, nil
37+
return false, err
3838
}
3939

4040
return true, nil
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2026 The kpt Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package starlark
16+
17+
import (
18+
"testing"
19+
20+
"github.com/kptdev/krm-functions-sdk/go/fn"
21+
"github.com/stretchr/testify/assert"
22+
)
23+
24+
func TestProcess_SetNamespace(t *testing.T) {
25+
input := `apiVersion: config.kubernetes.io/v1
26+
kind: ResourceList
27+
items:
28+
- apiVersion: apps/v1
29+
kind: Deployment
30+
metadata:
31+
name: my-app
32+
namespace: old-namespace
33+
functionConfig:
34+
apiVersion: fn.kpt.dev/v1alpha1
35+
kind: StarlarkRun
36+
metadata:
37+
name: set-namespace
38+
source: |
39+
def run(r, ns_value):
40+
for resource in r:
41+
resource["metadata"]["namespace"] = ns_value
42+
run(ctx.resource_list["items"], "new-namespace")
43+
`
44+
rl, err := fn.ParseResourceList([]byte(input))
45+
assert.NoError(t, err)
46+
47+
ok, err := Process(rl)
48+
assert.NoError(t, err)
49+
assert.True(t, ok)
50+
assert.Equal(t, "new-namespace", rl.Items[0].GetNamespace())
51+
}
52+
53+
func TestProcess_InvalidScript(t *testing.T) {
54+
input := `apiVersion: config.kubernetes.io/v1
55+
kind: ResourceList
56+
items: []
57+
functionConfig:
58+
apiVersion: fn.kpt.dev/v1alpha1
59+
kind: StarlarkRun
60+
metadata:
61+
name: bad-script
62+
source: |
63+
this is not valid starlark!!!
64+
`
65+
rl, err := fn.ParseResourceList([]byte(input))
66+
assert.NoError(t, err)
67+
68+
ok, err := Process(rl)
69+
assert.Error(t, err)
70+
assert.False(t, ok)
71+
assert.Len(t, rl.Results, 1)
72+
}

internal/builtins/starlark/starlark.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,18 @@ import (
2424

2525
const ImageName = "ghcr.io/kptdev/krm-functions-catalog/starlark"
2626

27-
func init() {
28-
registry.Register(&StarlarkRunner{})
27+
//nolint:gochecknoinits
28+
func init() { Register() }
29+
30+
func Register() {
31+
registry.Register(&Runner{})
2932
}
3033

31-
type StarlarkRunner struct{}
34+
type Runner struct{}
3235

33-
func (s *StarlarkRunner) ImageName() string { return ImageName }
36+
func (s *Runner) ImageName() string { return ImageName }
3437

35-
func (s *StarlarkRunner) Run(r io.Reader, w io.Writer) error {
38+
func (s *Runner) Run(r io.Reader, w io.Writer) error {
3639
input, err := io.ReadAll(r)
3740
if err != nil {
3841
return fmt.Errorf("reading input: %w", err)
@@ -41,13 +44,13 @@ func (s *StarlarkRunner) Run(r io.Reader, w io.Writer) error {
4144
if err != nil {
4245
return fmt.Errorf("parsing ResourceList: %w", err)
4346
}
44-
if _, err := Process(rl); err != nil {
45-
return err
46-
}
47+
_, processErr := Process(rl)
4748
out, err := rl.ToYAML()
4849
if err != nil {
4950
return err
5051
}
51-
_, err = w.Write(out)
52-
return err
52+
if _, err = w.Write(out); err != nil {
53+
return err
54+
}
55+
return processErr
5356
}

0 commit comments

Comments
 (0)