Files
kustomize/kyaml/runfn/runfn_test.go
Davanum Srinivas d32eacf034 Remove starlark support
Signed-off-by: Davanum Srinivas <davanum@gmail.com>
2024-09-23 16:52:05 -04:00

1285 lines
28 KiB
Go

// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package runfn
import (
"bytes"
"fmt"
"os"
"os/user"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/copyutil"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/filesys"
"sigs.k8s.io/kustomize/kyaml/fn/runtime/container"
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
const (
ValueReplacerYAMLData = `apiVersion: v1
kind: ValueReplacer
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example.com/image:version
config.kubernetes.io/local-config: "true"
stringMatch: Deployment
replace: StatefulSet
`
)
func currentUser() (*user.User, error) {
return &user.User{
Uid: "1",
Gid: "2",
}, nil
}
func TestRunFns_init(t *testing.T) {
instance := RunFns{}
instance.init()
if !assert.Equal(t, instance.Input, os.Stdin) {
t.FailNow()
}
if !assert.Equal(t, instance.Output, os.Stdout) {
t.FailNow()
}
api, err := yaml.Parse(`apiVersion: apps/v1
kind:
`)
spec := runtimeutil.FunctionSpec{
Container: runtimeutil.ContainerSpec{
Image: "example.com:version",
},
}
if !assert.NoError(t, err) {
return
}
filter, _ := instance.functionFilterProvider(spec, api, currentUser)
c := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, "nobody")
cf := &c
cf.Exec.FunctionConfig = api
assert.Equal(t, cf, filter)
}
func TestRunFns_initAsCurrentUser(t *testing.T) {
instance := RunFns{
AsCurrentUser: true,
}
instance.init()
if !assert.Equal(t, instance.Input, os.Stdin) {
t.FailNow()
}
if !assert.Equal(t, instance.Output, os.Stdout) {
t.FailNow()
}
api, err := yaml.Parse(`apiVersion: apps/v1
kind:
`)
spec := runtimeutil.FunctionSpec{
Container: runtimeutil.ContainerSpec{
Image: "example.com:version",
},
}
if !assert.NoError(t, err) {
return
}
filter, _ := instance.functionFilterProvider(spec, api, currentUser)
c := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, "1:2")
cf := &c
cf.Exec.FunctionConfig = api
assert.Equal(t, cf, filter)
}
func TestRunFns_Execute__initGlobalScope(t *testing.T) {
instance := RunFns{GlobalScope: true}
instance.init()
if !assert.Equal(t, instance.Input, os.Stdin) {
t.FailNow()
}
if !assert.Equal(t, instance.Output, os.Stdout) {
t.FailNow()
}
api, err := yaml.Parse(`apiVersion: apps/v1
kind:
`)
if !assert.NoError(t, err) {
return
}
spec := runtimeutil.FunctionSpec{
Container: runtimeutil.ContainerSpec{
Image: "example.com:version",
},
}
if !assert.NoError(t, err) {
return
}
filter, _ := instance.functionFilterProvider(spec, api, currentUser)
c := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, "nobody")
cf := &c
cf.Exec.FunctionConfig = api
cf.Exec.GlobalScope = true
assert.Equal(t, cf, filter)
}
func TestRunFns_Execute__initDefault(t *testing.T) {
b := &bytes.Buffer{}
var tests = []struct {
instance RunFns
expected RunFns
name string
}{
{
instance: RunFns{},
name: "empty",
expected: RunFns{Output: os.Stdout, Input: os.Stdin, NoFunctionsFromInput: getFalse()},
},
{
name: "explicit output",
instance: RunFns{Output: b},
expected: RunFns{Output: b, Input: os.Stdin, NoFunctionsFromInput: getFalse()},
},
{
name: "explicit input",
instance: RunFns{Input: b},
expected: RunFns{Output: os.Stdout, Input: b, NoFunctionsFromInput: getFalse()},
},
{
name: "explicit functions -- no functions from input",
instance: RunFns{Functions: []*yaml.RNode{{}}},
expected: RunFns{Output: os.Stdout, Input: os.Stdin, NoFunctionsFromInput: getTrue(), Functions: []*yaml.RNode{{}}},
},
{
name: "explicit functions -- yes functions from input",
instance: RunFns{Functions: []*yaml.RNode{{}}, NoFunctionsFromInput: getFalse()},
expected: RunFns{Output: os.Stdout, Input: os.Stdin, NoFunctionsFromInput: getFalse(), Functions: []*yaml.RNode{{}}},
},
{
name: "explicit functions in paths -- no functions from input",
instance: RunFns{FunctionPaths: []string{"foo"}},
expected: RunFns{
Output: os.Stdout,
Input: os.Stdin,
NoFunctionsFromInput: getTrue(),
FunctionPaths: []string{"foo"},
},
},
{
name: "functions in paths -- yes functions from input",
instance: RunFns{FunctionPaths: []string{"foo"}, NoFunctionsFromInput: getFalse()},
expected: RunFns{
Output: os.Stdout,
Input: os.Stdin,
NoFunctionsFromInput: getFalse(),
FunctionPaths: []string{"foo"},
},
},
{
name: "explicit directories in mounts",
instance: RunFns{StorageMounts: []runtimeutil.StorageMount{{MountType: "volume", Src: "myvol", DstPath: "/local/"}}},
expected: RunFns{
Output: os.Stdout,
Input: os.Stdin,
NoFunctionsFromInput: getFalse(),
StorageMounts: []runtimeutil.StorageMount{{MountType: "volume", Src: "myvol", DstPath: "/local/"}},
},
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
(&tt.instance).init()
(&tt.instance).functionFilterProvider = nil
if !assert.Equal(t, tt.expected, tt.instance) {
t.FailNow()
}
})
}
}
func getTrue() *bool {
t := true
return &t
}
func getFalse() *bool {
f := false
return &f
}
// TestRunFns_getFilters tests how filters are found and sorted
func TestRunFns_getFilters(t *testing.T) {
type f struct {
// path to function file and string value to write
path, value string
// if true, create the function in a separate directory from
// the config, and provide it through FunctionPaths
outOfPackage bool
// if true, create the function as an explicit Functions input
explicitFunction bool
// if true and outOfPackage is true, create a new directory
// for this function separate from the previous one. If
// false and outOfPackage is true, create the function in
// the directory created for the last outOfPackage function.
newFnPath bool
}
var tests = []struct {
// function files to write
in []f
// images to be run in a specific order
out []string
// images to be run in a specific order -- computed from directory path
outFn func(string) []string
// expected Error
error string
// name of the test
name string
// value to set for NoFunctionsFromInput
noFunctionsFromInput *bool
disableContainers bool
}{
// Test
//
//
{name: "single implicit function",
in: []f{
{
path: filepath.Join("foo", "bar.yaml"),
value: `
apiVersion: example.com/v1alpha1
kind: ExampleFunction
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example.com/image:v1.0.0
config.kubernetes.io/local-config: "true"
`,
},
},
out: []string{"gcr.io/example.com/image:v1.0.0"},
},
{
name: "no function spec",
in: []f{
{
explicitFunction: true,
value: `
apiVersion: example.com/v1alpha1
kind: ExampleFunction
metadata:
annotations:
foo: bar
`,
},
},
},
{
name: "invalid input object",
in: []f{
{
explicitFunction: true,
value: `
foo: bar
`,
},
},
error: "failed to get FunctionSpec: failed to get ResourceMeta: missing Resource metadata",
},
// Test
//
//
{name: "defer_failure",
in: []f{
{
path: filepath.Join("foo", "bar.yaml"),
value: `
apiVersion: example.com/v1alpha1
kind: ExampleFunction
metadata:
annotations:
config.kubernetes.io/function: |
deferFailure: true
container:
image: gcr.io/example.com/image:v1.0.0
config.kubernetes.io/local-config: "true"
`,
},
},
out: []string{"gcr.io/example.com/image:v1.0.0 deferFailure: true"},
},
{
name: "parse_failure",
in: []f{
{
path: filepath.Join("foo", "bar.yaml"),
value: `
apiVersion: example.com/v1alpha1
kind: ExampleFunction
metadata:
annotations:
config.kubernetes.io/function: |
containeeer:
image: gcr.io/example.com/image:v1.0.0
`,
},
},
error: "config.kubernetes.io/function unmarshal error: error unmarshaling JSON: while decoding JSON: json: unknown field \"containeeer\"",
},
{name: "disable containers",
in: []f{
{
path: filepath.Join("foo", "bar.yaml"),
value: `
apiVersion: example.com/v1alpha1
kind: ExampleFunction
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example.com/image:v1.0.0
config.kubernetes.io/local-config: "true"
`,
},
},
out: nil,
disableContainers: true,
},
// Test
//
//
{name: "sort functions -- deepest first",
in: []f{
{
path: "a.yaml",
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: a
`,
},
{
path: filepath.Join("foo", "b.yaml"),
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: b
`,
},
},
out: []string{"b", "a"},
},
// Test
//
//
{name: "sort functions -- skip implicit with output of package",
in: []f{
{
path: filepath.Join("foo", "a.yaml"),
outOfPackage: true, // out of package is run last
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: a
`,
},
{
path: "b.yaml",
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: b
`,
},
},
out: []string{"a"},
},
// Test
//
//
{name: "sort functions -- skip implicit",
noFunctionsFromInput: getTrue(),
in: []f{
{
path: filepath.Join("foo", "a.yaml"),
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: a
`,
},
{
path: "b.yaml",
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: b
`,
},
},
out: nil,
},
// Test
//
//
{name: "sort functions -- include implicit",
noFunctionsFromInput: getFalse(),
in: []f{
{
path: filepath.Join("foo", "a.yaml"),
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: a
`,
},
{
path: "b.yaml",
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: b
`,
},
},
out: []string{"a", "b"},
},
// Test
//
//
{name: "sort functions -- implicit first",
noFunctionsFromInput: getFalse(),
in: []f{
{
path: filepath.Join("foo", "a.yaml"),
outOfPackage: true, // out of package is run last
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: a
`,
},
{
path: "b.yaml",
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: b
`,
},
},
out: []string{"b", "a"},
},
// Test
//
//
{name: "explicit functions",
in: []f{
{
explicitFunction: true,
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: c
`,
},
{
path: "b.yaml",
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: b
`,
},
},
out: []string{"c"},
},
// Test
//
//
{name: "sort functions -- implicit first",
noFunctionsFromInput: getFalse(),
in: []f{
{
explicitFunction: true,
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: c
`,
},
{
path: filepath.Join("foo", "a.yaml"),
outOfPackage: true, // out of package is run last
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: a
`,
},
{
path: "b.yaml",
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: b
`,
},
},
out: []string{"b", "a", "c"},
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
// setup the test directory
d := setupTest(t)
// write the functions to files
var fnPaths []string
var parsedFns []*yaml.RNode
var fnPath string
var err error
for _, f := range tt.in {
// get the location for the file
var dir string
switch {
case f.outOfPackage:
// if out of package, write to a separate temp directory
if f.newFnPath || fnPath == "" {
// create a new fn directory
fnPath = t.TempDir()
fnPaths = append(fnPaths, fnPath)
}
dir = fnPath
case f.explicitFunction:
parsedFns = append(parsedFns, yaml.MustParse(f.value))
default:
// if in package, write to the dir containing the configs
dir = d
}
if !f.explicitFunction {
// create the parent dir and write the file
err = os.MkdirAll(filepath.Join(dir, filepath.Dir(f.path)), 0700)
if !assert.NoError(t, err) {
t.FailNow()
}
err := os.WriteFile(filepath.Join(dir, f.path), []byte(f.value), 0600)
if !assert.NoError(t, err) {
t.FailNow()
}
}
}
// init the instance
r := &RunFns{
DisableContainers: tt.disableContainers,
FunctionPaths: fnPaths,
Functions: parsedFns,
Path: d,
NoFunctionsFromInput: tt.noFunctionsFromInput,
}
r.init()
// get the filters which would be run
var results []string
_, fltrs, _, err := r.getNodesAndFilters()
if tt.error != "" {
if !assert.EqualError(t, err, tt.error) {
t.FailNow()
}
return
}
if !assert.NoError(t, err) {
t.FailNow()
}
for _, f := range fltrs {
results = append(results, strings.TrimSpace(fmt.Sprintf("%v", f)))
}
// compare the actual ordering to the expected ordering
if tt.outFn != nil {
if !assert.Equal(t, tt.outFn(d), results) {
t.FailNow()
}
} else {
if !assert.Equal(t, tt.out, results) {
t.FailNow()
}
}
})
}
}
func TestRunFns_sortFns(t *testing.T) {
testCases := []struct {
name string
nodes []*yaml.RNode
expectedImages []string
expectedErrMsg string
}{
{
name: "multiple functions in the same file are ordered by index",
nodes: []*yaml.RNode{
yaml.MustParse(`
metadata:
annotations:
config.kubernetes.io/path: functions.yaml
config.kubernetes.io/index: 1
config.kubernetes.io/function: |
container:
image: a
`),
yaml.MustParse(`
metadata:
annotations:
config.kubernetes.io/path: functions.yaml
config.kubernetes.io/index: 0
config.kubernetes.io/function: |
container:
image: b
`),
},
expectedImages: []string{"b", "a"},
},
{
name: "non-integer value in index annotation is an error",
nodes: []*yaml.RNode{
yaml.MustParse(`
metadata:
annotations:
config.kubernetes.io/path: functions.yaml
config.kubernetes.io/index: 0
config.kubernetes.io/function: |
container:
image: a
`),
yaml.MustParse(`
metadata:
annotations:
config.kubernetes.io/path: functions.yaml
config.kubernetes.io/index: abc
config.kubernetes.io/function: |
container:
image: b
`),
},
expectedErrMsg: "strconv.Atoi: parsing \"abc\": invalid syntax",
},
}
for i := range testCases {
test := testCases[i]
t.Run(test.name, func(t *testing.T) {
packageBuff := &kio.PackageBuffer{
Nodes: test.nodes,
}
err := sortFns(packageBuff)
if test.expectedErrMsg != "" {
if !assert.Error(t, err) {
t.FailNow()
}
assert.Equal(t, test.expectedErrMsg, err.Error())
return
}
if !assert.NoError(t, err) {
t.FailNow()
}
var images []string
for _, n := range packageBuff.Nodes {
spec, err := runtimeutil.GetFunctionSpec(n)
if !assert.NoError(t, err) {
t.FailNow()
}
images = append(images, spec.Container.Image)
}
assert.Equal(t, test.expectedImages, images)
})
}
}
func TestRunFns_network(t *testing.T) {
tests := []struct {
name string
input string
network bool
expectNetwork bool
error string
}{
{
name: "imperative false, declarative false",
input: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: a
network: false
`,
network: false,
expectNetwork: false,
},
{
name: "imperative true, declarative false",
input: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: a
network: false
`,
network: true,
expectNetwork: false,
},
{
name: "imperative true, declarative true",
input: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: a
network: true
`,
network: true,
expectNetwork: true,
},
{
name: "imperative false, declarative true",
input: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: a
network: true
`,
network: false,
error: "network required but not enabled with --network",
},
}
for i := range tests {
tt := tests[i]
fn := yaml.MustParse(tt.input)
t.Run(tt.name, func(t *testing.T) {
// init the instance
r := &RunFns{
Functions: []*yaml.RNode{fn},
Network: tt.network,
}
r.init()
_, fltrs, _, err := r.getNodesAndFilters()
if tt.error != "" {
if !assert.EqualError(t, err, tt.error) {
t.FailNow()
}
return
}
if !assert.NoError(t, err) {
t.FailNow()
}
fltr := fltrs[0].(*container.Filter)
if !assert.Equal(t, tt.expectNetwork, fltr.Network) {
t.FailNow()
}
})
}
}
func TestCmd_Execute(t *testing.T) {
dir := setupTest(t)
// write a test filter to the directory of configuration
if !assert.NoError(t, os.WriteFile(
filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
return
}
instance := RunFns{Path: dir, functionFilterProvider: getFilterProvider(t)}
if !assert.NoError(t, instance.Execute()) {
t.FailNow()
}
b, err := os.ReadFile(
filepath.Join(dir, "java", "java-deployment.resource.yaml"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.Contains(t, string(b), "kind: StatefulSet")
}
type TestFilter struct {
invoked bool
Exit error
}
func (f *TestFilter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
f.invoked = true
return input, nil
}
func (f *TestFilter) GetExit() error {
return f.Exit
}
func TestCmd_Execute_deferFailure(t *testing.T) {
dir := setupTest(t)
// write a test filter to the directory of configuration
if !assert.NoError(t, os.WriteFile(
filepath.Join(dir, "filter1.yaml"), []byte(`apiVersion: v1
kind: ValueReplacer
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: 1
config.kubernetes.io/local-config: "true"
stringMatch: Deployment
replace: StatefulSet
`), 0600)) {
t.FailNow()
}
// write a test filter to the directory of configuration
if !assert.NoError(t, os.WriteFile(
filepath.Join(dir, "filter2.yaml"), []byte(`apiVersion: v1
kind: ValueReplacer
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: 2
config.kubernetes.io/local-config: "true"
stringMatch: Deployment
replace: StatefulSet
`), 0600)) {
t.FailNow()
}
var fltrs []*TestFilter
instance := RunFns{
Path: dir,
functionFilterProvider: func(f runtimeutil.FunctionSpec, node *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error) {
tf := &TestFilter{
Exit: errors.Errorf("message: %s", f.Container.Image),
}
fltrs = append(fltrs, tf)
return tf, nil
},
}
instance.init()
err := instance.Execute()
// make sure all filters were run
if !assert.Equal(t, 2, len(fltrs)) {
t.FailNow()
}
for i := range fltrs {
if !assert.True(t, fltrs[i].invoked) {
t.FailNow()
}
}
if !assert.EqualError(t, err, "message: 1\n---\nmessage: 2") {
t.FailNow()
}
b, err := os.ReadFile(
filepath.Join(dir, "java", "java-deployment.resource.yaml"))
if !assert.NoError(t, err) {
t.FailNow()
}
// files weren't changed because there was an error
assert.Contains(t, string(b), "kind: Deployment")
}
// TestCmd_Execute_setOutput tests the execution of a filter reading and writing to a dir
func TestCmd_Execute_setFunctionPaths(t *testing.T) {
dir := setupTest(t)
// write a test filter to a separate directory
tmpF, err := os.CreateTemp("", "filter*.yaml")
if !assert.NoError(t, err) {
return
}
os.RemoveAll(tmpF.Name())
if !assert.NoError(t, os.WriteFile(tmpF.Name(), []byte(ValueReplacerYAMLData), 0600)) {
return
}
// run the functions, providing the path to the directory of filters
instance := RunFns{
FunctionPaths: []string{tmpF.Name()},
Path: dir,
functionFilterProvider: getFilterProvider(t),
}
// initialize the defaults
instance.init()
err = instance.Execute()
if !assert.NoError(t, err) {
return
}
b, err := os.ReadFile(
filepath.Join(dir, "java", "java-deployment.resource.yaml"))
if !assert.NoError(t, err) {
return
}
assert.Contains(t, string(b), "kind: StatefulSet")
}
// TestCmd_Execute_setOutput tests the execution of a filter using an io.Writer as output
func TestCmd_Execute_setOutput(t *testing.T) {
dir := setupTest(t)
// write a test filter
if !assert.NoError(t, os.WriteFile(
filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
return
}
out := &bytes.Buffer{}
instance := RunFns{
Output: out, // write to out
Path: dir,
functionFilterProvider: getFilterProvider(t),
}
// initialize the defaults
instance.init()
if !assert.NoError(t, instance.Execute()) {
return
}
b, err := os.ReadFile(
filepath.Join(dir, "java", "java-deployment.resource.yaml"))
if !assert.NoError(t, err) {
return
}
assert.NotContains(t, string(b), "kind: StatefulSet")
assert.Contains(t, out.String(), "kind: StatefulSet")
}
// TestCmd_Execute_setInput tests the execution of a filter using an io.Reader as input
func TestCmd_Execute_setInput(t *testing.T) {
dir := setupTest(t)
if !assert.NoError(t, os.WriteFile(
filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
return
}
read, err := kio.LocalPackageReader{PackagePath: dir}.Read()
if !assert.NoError(t, err) {
t.FailNow()
}
input := &bytes.Buffer{}
if !assert.NoError(t, kio.ByteWriter{Writer: input}.Write(read)) {
t.FailNow()
}
outDir := t.TempDir()
if !assert.NoError(t, os.WriteFile(
filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
return
}
instance := RunFns{
Input: input, // read from input
Path: outDir,
functionFilterProvider: getFilterProvider(t),
}
// initialize the defaults
instance.init()
if !assert.NoError(t, instance.Execute()) {
return
}
b, err := os.ReadFile(
filepath.Join(outDir, "java", "java-deployment.resource.yaml"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.Contains(t, string(b), "kind: StatefulSet")
}
// TestCmd_Execute_enableLogSteps tests the execution of a filter with LogSteps enabled.
func TestCmd_Execute_enableLogSteps(t *testing.T) {
dir := setupTest(t)
// write a test filter to the directory of configuration
if !assert.NoError(t, os.WriteFile(
filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
return
}
logs := &bytes.Buffer{}
instance := RunFns{
Path: dir,
functionFilterProvider: getFilterProvider(t),
LogSteps: true,
LogWriter: logs,
}
if !assert.NoError(t, instance.Execute()) {
t.FailNow()
}
b, err := os.ReadFile(
filepath.Join(dir, "java", "java-deployment.resource.yaml"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.Contains(t, string(b), "kind: StatefulSet")
assert.Equal(t, "Running unknown-type function\n", logs.String())
}
func getGeneratorFilterProvider(t *testing.T) func(runtimeutil.FunctionSpec, *yaml.RNode, currentUserFunc) (kio.Filter, error) {
t.Helper()
return func(f runtimeutil.FunctionSpec, node *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error) {
return kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
if f.Container.Image == "generate" {
node, err := yaml.Parse("kind: generated")
if !assert.NoError(t, err) {
t.FailNow()
}
return append(items, node), nil
}
return items, nil
}), nil
}
}
func TestRunFns_ContinueOnEmptyResult(t *testing.T) {
fn1, err := yaml.Parse(`
kind: fakefn
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: pass
`)
if !assert.NoError(t, err) {
t.FailNow()
}
fn2, err := yaml.Parse(`
kind: fakefn
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: generate
`)
if !assert.NoError(t, err) {
t.FailNow()
}
var test = []struct {
ContinueOnEmptyResult bool
ExpectedOutput string
}{
{
ContinueOnEmptyResult: false,
ExpectedOutput: "",
},
{
ContinueOnEmptyResult: true,
ExpectedOutput: "kind: generated\n",
},
}
for i := range test {
ouputBuffer := bytes.Buffer{}
instance := RunFns{
Input: bytes.NewReader([]byte{}),
Output: &ouputBuffer,
Functions: []*yaml.RNode{fn1, fn2},
functionFilterProvider: getGeneratorFilterProvider(t),
ContinueOnEmptyResult: test[i].ContinueOnEmptyResult,
}
if !assert.NoError(t, instance.Execute()) {
t.FailNow()
}
assert.Equal(t, test[i].ExpectedOutput, ouputBuffer.String())
}
}
// setupTest initializes a temp test directory containing test data
func setupTest(t *testing.T) string {
t.Helper()
dir := t.TempDir()
_, filename, _, ok := runtime.Caller(0)
if !assert.True(t, ok) {
t.FailNow()
}
ds, err := filepath.Abs(filepath.Join(filepath.Dir(filename), "test", "testdata"))
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, copyutil.CopyDir(filesys.MakeFsOnDisk(), ds, dir)) {
t.FailNow()
}
cwd, err := os.Getwd()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, os.Chdir(filepath.Dir(dir))) {
t.FailNow()
}
// Change back the current working directory when the test finishes
t.Cleanup(func() {
if !assert.NoError(t, os.Chdir(cwd)) {
t.FailNow()
}
})
return dir
}
// getFilterProvider fakes the creation of a filter, replacing the ContainerFiler with
// a filter to s/kind: Deployment/kind: StatefulSet/g.
// this can be used to simulate running a filter.
func getFilterProvider(t *testing.T) func(runtimeutil.FunctionSpec, *yaml.RNode, currentUserFunc) (kio.Filter, error) {
t.Helper()
return func(f runtimeutil.FunctionSpec, node *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error) {
// parse the filter from the input
filter := yaml.YFilter{}
b := &bytes.Buffer{}
e := yaml.NewEncoder(b)
if !assert.NoError(t, e.Encode(node.YNode())) {
t.FailNow()
}
e.Close()
d := yaml.NewDecoder(b)
if !assert.NoError(t, d.Decode(&filter)) {
t.FailNow()
}
return filters.Modifier{
Filters: []yaml.YFilter{{Filter: yaml.Lookup("kind")}, filter},
}, nil
}
}
func TestRunFns_mergeContainerEnv(t *testing.T) {
testcases := []struct {
name string
instance RunFns
inputEnvs []string
expect runtimeutil.ContainerEnv
}{
{
name: "all empty",
instance: RunFns{},
expect: *runtimeutil.NewContainerEnv(),
},
{
name: "empty command line envs",
instance: RunFns{},
inputEnvs: []string{"foo=bar"},
expect: *runtimeutil.NewContainerEnvFromStringSlice([]string{"foo=bar"}),
},
{
name: "empty declarative envs",
instance: RunFns{
Env: []string{"foo=bar"},
},
expect: *runtimeutil.NewContainerEnvFromStringSlice([]string{"foo=bar"}),
},
{
name: "same key",
instance: RunFns{
Env: []string{"foo=bar", "foo"},
},
inputEnvs: []string{"foo=bar1", "bar"},
expect: *runtimeutil.NewContainerEnvFromStringSlice([]string{"foo=bar", "bar", "foo"}),
},
{
name: "same exported key",
instance: RunFns{
Env: []string{"foo=bar", "foo"},
},
inputEnvs: []string{"foo1=bar1", "foo"},
expect: *runtimeutil.NewContainerEnvFromStringSlice([]string{"foo=bar", "foo1=bar1", "foo"}),
},
}
for i := range testcases {
tc := testcases[i]
t.Run(tc.name, func(t *testing.T) {
envs := tc.instance.mergeContainerEnv(tc.inputEnvs)
assert.Equal(t, tc.expect.GetDockerFlags(), runtimeutil.NewContainerEnvFromStringSlice(envs).GetDockerFlags())
})
}
}