// Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 package commands import ( "io" "os" "strings" "testing" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "sigs.k8s.io/kustomize/kyaml/runfn" ) // TestRunFnCommand_preRunE verifies that preRunE correctly parses the commandline // flags and arguments into the RunFns structure to be executed. func TestRunFnCommand_preRunE(t *testing.T) { wd, err := os.Getwd() assert.NoError(t, err) tests := []struct { name string args []string expected string expectedStruct *runfn.RunFns err string path string input io.Reader output io.Writer functionPaths []string network bool mount []string }{ { name: "config map", args: []string{"run", "dir", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"}, path: "dir", expected: ` metadata: name: function-input annotations: config.kubernetes.io/function: | container: {image: 'foo:bar'} data: {a: b, c: d, e: f} kind: ConfigMap apiVersion: v1 `, }, { name: "config map stdin / stdout", args: []string{"run", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"}, input: os.Stdin, output: os.Stdout, expected: ` metadata: name: function-input annotations: config.kubernetes.io/function: | container: {image: 'foo:bar'} data: {a: b, c: d, e: f} kind: ConfigMap apiVersion: v1 `, }, { name: "config map dry-run", args: []string{"run", "dir", "--image", "foo:bar", "--dry-run", "--", "a=b", "c=d", "e=f"}, output: os.Stdout, path: "dir", expected: ` metadata: name: function-input annotations: config.kubernetes.io/function: | container: {image: 'foo:bar'} data: {a: b, c: d, e: f} kind: ConfigMap apiVersion: v1 `, }, { name: "config map no args", args: []string{"run", "dir", "--image", "foo:bar"}, path: "dir", expected: ` metadata: name: function-input annotations: config.kubernetes.io/function: | container: {image: 'foo:bar'} data: {} kind: ConfigMap apiVersion: v1 `, }, { name: "network enabled", args: []string{"run", "dir", "--image", "foo:bar", "--network"}, path: "dir", network: true, expected: ` metadata: name: function-input annotations: config.kubernetes.io/function: | container: {image: 'foo:bar', network: true} data: {} kind: ConfigMap apiVersion: v1 `, }, { name: "with network name", args: []string{"run", "dir", "--image", "foo:bar", "--network"}, path: "dir", network: true, expected: ` metadata: name: function-input annotations: config.kubernetes.io/function: | container: {image: 'foo:bar', network: true} data: {} kind: ConfigMap apiVersion: v1 `, }, { name: "custom kind", args: []string{"run", "dir", "--image", "foo:bar", "--", "Foo", "g=h"}, path: "dir", expected: ` metadata: name: function-input annotations: config.kubernetes.io/function: | container: {image: 'foo:bar'} data: {g: h} kind: Foo apiVersion: v1 `, }, { name: "custom kind '=' in data", args: []string{"run", "dir", "--image", "foo:bar", "--", "Foo", "g=h", "i=j=k"}, path: "dir", expected: ` metadata: name: function-input annotations: config.kubernetes.io/function: | container: {image: 'foo:bar'} data: {g: h, i: j=k} kind: Foo apiVersion: v1 `, }, { name: "star", args: []string{"run", "dir", "--enable-star", "--star-path", "a/b/c", "--star-name", "foo", "--", "Foo", "g=h"}, path: "dir", expected: ` metadata: name: function-input annotations: config.kubernetes.io/function: | starlark: {path: a/b/c, name: foo} data: {g: h} kind: Foo apiVersion: v1 `, }, { name: "star-not-enabled", args: []string{"run", "dir", "--star-path", "a/b/c", "--star-name", "foo", "--", "Foo", "g=h"}, path: "dir", err: "must specify --enable-star with --star-path", }, { name: "image-star-not-enabled", args: []string{"run", "dir", "--image", "some_image", "--star-path", "a/b/c", "--star-name", "foo", "--", "Foo", "g=h"}, path: "dir", err: "must specify --enable-star with --star-path", }, { name: "star-enabled", args: []string{"run", "dir", "--enable-star"}, path: "dir", expectedStruct: &runfn.RunFns{ Path: "dir", EnableStarlark: true, Env: []string{}, WorkingDir: wd, }, }, { name: "function paths", args: []string{"run", "dir", "--fn-path", "path1", "--fn-path", "path2"}, path: "dir", functionPaths: []string{"path1", "path2"}, }, { name: "custom kind with function paths", args: []string{ "run", "dir", "--fn-path", "path", "--image", "foo:bar", "--", "Foo", "g=h", "i=j=k"}, path: "dir", functionPaths: []string{"path"}, expected: ` metadata: name: function-input annotations: config.kubernetes.io/function: | container: {image: 'foo:bar'} data: {g: h, i: j=k} kind: Foo apiVersion: v1 `, }, { name: "custom kind with storage mounts", args: []string{ "run", "dir", "--mount", "type=bind,src=/mount/path,dst=/local/", "--mount", "type=volume,src=myvol,dst=/local/", "--mount", "type=tmpfs,dst=/local/", "--image", "foo:bar", "--", "Foo", "g=h", "i=j=k"}, path: "dir", mount: []string{"type=bind,src=/mount/path,dst=/local/", "type=volume,src=myvol,dst=/local/", "type=tmpfs,dst=/local/"}, expected: ` metadata: name: function-input annotations: config.kubernetes.io/function: | container: {image: 'foo:bar'} data: {g: h, i: j=k} kind: Foo apiVersion: v1 `, }, { name: "results_dir", args: []string{"run", "dir", "--results-dir", "foo/", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"}, path: "dir", expectedStruct: &runfn.RunFns{ Path: "dir", ResultsDir: "foo/", Env: []string{}, WorkingDir: wd, }, expected: ` metadata: name: function-input annotations: config.kubernetes.io/function: | container: {image: 'foo:bar'} data: {a: b, c: d, e: f} kind: ConfigMap apiVersion: v1 `, }, { name: "config map multi args", args: []string{"run", "dir", "dir2", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"}, err: "0 or 1 arguments supported", }, { name: "config map not image", args: []string{"run", "dir", "--", "a=b", "c=d", "e=f"}, err: "must specify --image", }, { name: "config map bad data", args: []string{"run", "dir", "--image", "foo:bar", "--", "a=b", "c", "e=f"}, err: "must have keys and values separated by", }, { name: "log steps", args: []string{"run", "dir", "--log-steps"}, path: "dir", expectedStruct: &runfn.RunFns{ Path: "dir", LogSteps: true, Env: []string{}, WorkingDir: wd, }, }, { name: "envs", args: []string{"run", "dir", "--env", "FOO=BAR", "-e", "BAR"}, path: "dir", expectedStruct: &runfn.RunFns{ Path: "dir", Env: []string{"FOO=BAR", "BAR"}, WorkingDir: wd, }, }, { name: "as current user", args: []string{"run", "dir", "--as-current-user"}, path: "dir", expectedStruct: &runfn.RunFns{ Path: "dir", AsCurrentUser: true, Env: []string{}, WorkingDir: wd, }, }, } for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { r := GetRunFnRunner("kustomize") // Don't run the actual command r.Command.Run = nil r.Command.RunE = func(cmd *cobra.Command, args []string) error { return nil } r.Command.SilenceErrors = true r.Command.SilenceUsage = true // hack due to https://github.com/spf13/cobra/issues/42 root := &cobra.Command{Use: "root"} root.AddCommand(r.Command) root.SetArgs(tt.args) // error case err := r.Command.Execute() if tt.err != "" { if !assert.Error(t, err) { t.FailNow() } if !assert.Contains(t, err.Error(), tt.err) { t.FailNow() } // don't check anything else in error case return } // non-error case if !assert.NoError(t, err) { t.FailNow() } // check if Input was set if !assert.Equal(t, tt.input, r.RunFns.Input) { t.FailNow() } // check if Output was set if !assert.Equal(t, tt.output, r.RunFns.Output) { t.FailNow() } // check if Path was set if !assert.Equal(t, tt.path, r.RunFns.Path) { t.FailNow() } // check if Network was set if tt.network { if !assert.Equal(t, tt.network, r.RunFns.Network) { t.FailNow() } } else { if !assert.Equal(t, false, r.RunFns.Network) { t.FailNow() } } // check if FunctionPaths were set if tt.functionPaths == nil { // make Equal work against flag default tt.functionPaths = []string{} } if !assert.Equal(t, tt.functionPaths, r.RunFns.FunctionPaths) { t.FailNow() } if !assert.Equal(t, r.RunFns, r.RunFns) { t.FailNow() } if !assert.Equal(t, toStorageMounts(tt.mount), r.RunFns.StorageMounts) { t.FailNow() } // check if Functions were set if tt.expected != "" { if !assert.Len(t, r.RunFns.Functions, 1) { t.FailNow() } actual := strings.TrimSpace(r.RunFns.Functions[0].MustString()) if !assert.Equal(t, strings.TrimSpace(tt.expected), actual) { t.FailNow() } } if tt.expectedStruct != nil { r.RunFns.Functions = nil tt.expectedStruct.FunctionPaths = tt.functionPaths if !assert.Equal(t, *tt.expectedStruct, r.RunFns) { t.FailNow() } } }) } }