/* 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()) }) } }