airshipctl/pkg/container/runfn/runfn_test.go
Ruslan Aliev d0e2f6dbbf Add runfn implementation with timeout support
Allows to limit execution time of KRM function.

Change-Id: I7fd8d97492512c6c5033375429906a1268f3818e
Signed-off-by: Ruslan Aliev <raliev@mirantis.com>
Closes: #544
Closes: #545
Closes: #597
2021-11-17 21:44:07 +00:00

1269 lines
28 KiB
Go

/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package runfn
import (
"bytes"
"fmt"
"io/ioutil"
"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/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{noCmdSet: 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, err := instance.functionFilterProvider(spec, api, currentUser)
assert.NoError(t, err)
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,
noCmdSet: 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",
},
}
filter, err := instance.functionFilterProvider(spec, api, currentUser)
assert.NoError(t, err)
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, noCmdSet: 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, err := instance.functionFilterProvider(spec, api, currentUser)
assert.NoError(t, err)
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
}
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
}
// TestRunFns_getFilters tests how filters are found and sorted
func TestRunFns_getFilters(t *testing.T) {
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
}{
// 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: `
foo: bar
`,
},
},
},
// 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"},
},
// Test
//
//
{name: "sort functions -- deepest first",
in: []f{
{
path: filepath.Join("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: filepath.Join("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: filepath.Join("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: filepath.Join("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: filepath.Join("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: filepath.Join("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: filepath.Join("b.yaml"),
value: `
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: b
`,
},
},
out: []string{"b", "a", "c"},
},
{name: "starlark-function-disabled",
in: []f{
{
path: filepath.Join("foo", "bar.yaml"),
value: `
apiVersion: example.com/v1alpha1
kind: ExampleFunction
metadata:
annotations:
config.kubernetes.io/function: |
starlark:
path: a/b/c
`,
},
},
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
// setup the test directory
d := setupTest(t)
defer os.RemoveAll(d)
// write the functions to files
var fnPaths []string
var parsedFns []*yaml.RNode
var fnPath string
var err error
for _, f := range tt.in {
getFileLocation(t, f, &d, &fnPath, &fnPaths, &parsedFns)
}
defer os.RemoveAll(fnPath)
// init the instance
r := &RunFns{
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 getFileLocation(t *testing.T, f f, d *string, fnPath *string, fnPaths *[]string, parsedFns *[]*yaml.RNode) {
// 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
var err error
*fnPath, err = ioutil.TempDir("", "kustomize-test")
if !assert.NoError(t, err) {
t.FailNow()
}
*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 = ioutil.WriteFile(filepath.Join(dir, f.path), []byte(f.value), 0600)
if !assert.NoError(t, err) {
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 := runtimeutil.GetFunctionSpec(n)
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, ok := fltrs[0].(*container.Filter)
assert.True(t, ok)
if !assert.Equal(t, tt.expectNetwork, fltr.Network) {
t.FailNow()
}
})
}
}
func TestCmd_Execute(t *testing.T) {
dir := setupTest(t)
defer os.RemoveAll(dir)
// write a test filter to the directory of configuration
if !assert.NoError(t, ioutil.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 := ioutil.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)
defer os.RemoveAll(dir)
// write a test filter to the directory of configuration
if !assert.NoError(t, ioutil.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, ioutil.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 := ioutil.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)
defer os.RemoveAll(dir)
// write a test filter to a separate directory
tmpF, err := ioutil.TempFile("", "filter*.yaml")
if !assert.NoError(t, err) {
return
}
os.RemoveAll(tmpF.Name())
if !assert.NoError(t, ioutil.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 := ioutil.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)
defer os.RemoveAll(dir)
// write a test filter
if !assert.NoError(t, ioutil.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 := ioutil.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)
defer os.RemoveAll(dir)
if !assert.NoError(t, ioutil.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, err := ioutil.TempDir("", "kustomize-test")
defer os.RemoveAll(outDir)
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, ioutil.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 := ioutil.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)
defer os.RemoveAll(dir)
// write a test filter to the directory of configuration
if !assert.NoError(t, ioutil.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 := ioutil.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) {
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 {
dir, err := ioutil.TempDir("", "kustomize-kyaml-test")
if !assert.NoError(t, err) {
t.FailNow()
}
_, 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(ds, dir)) {
t.FailNow()
}
if !assert.NoError(t, os.Chdir(filepath.Dir(dir))) {
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) {
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())
})
}
}