github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/thirdparty/cmdconfig/commands/cmdeval/cmdeval_test.go (about)

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package cmdeval
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/GoogleContainerTools/kpt/internal/fnruntime"
    16  	"github.com/GoogleContainerTools/kpt/internal/printer/fake"
    17  	"github.com/GoogleContainerTools/kpt/internal/testutil"
    18  	"github.com/GoogleContainerTools/kpt/thirdparty/kyaml/runfn"
    19  	"github.com/spf13/cobra"
    20  	"github.com/stretchr/testify/assert"
    21  	"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
    22  )
    23  
    24  // TestRunFnCommand_preRunE verifies that preRunE correctly parses the commandline
    25  // flags and arguments into the RunFns structure to be executed.
    26  func TestRunFnCommand_preRunE(t *testing.T) {
    27  	tempDir, err := os.MkdirTemp("", "")
    28  	if !assert.NoError(t, err) {
    29  		t.FailNow()
    30  	}
    31  	defer func() {
    32  		_ = os.RemoveAll(tempDir)
    33  	}()
    34  	defer testutil.Chdir(t, filepath.Dir(tempDir))()
    35  	dir := filepath.Base(tempDir)
    36  
    37  	tests := []struct {
    38  		name             string
    39  		args             []string
    40  		expectedFnConfig string
    41  		expectedFn       *runtimeutil.FunctionSpec
    42  		expectedStruct   *runfn.RunFns
    43  		expectedExecArgs []string
    44  		err              string
    45  		path             string
    46  		input            io.Reader
    47  		output           io.Writer
    48  		fnConfigPath     string
    49  		network          bool
    50  		mount            []string
    51  	}{
    52  		{
    53  			name: "config map",
    54  			args: []string{"eval", dir, "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
    55  			path: dir,
    56  			expectedFn: &runtimeutil.FunctionSpec{
    57  				Container: runtimeutil.ContainerSpec{
    58  					Image: "gcr.io/kpt-fn/foo:bar",
    59  				},
    60  			},
    61  			expectedFnConfig: `
    62  metadata:
    63    name: function-input
    64  data: {a: b, c: d, e: f}
    65  kind: ConfigMap
    66  apiVersion: v1
    67  `,
    68  		},
    69  		{
    70  			name:   "config map stdin / stdout",
    71  			args:   []string{"eval", "-", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
    72  			input:  os.Stdin,
    73  			output: &bytes.Buffer{},
    74  			expectedFn: &runtimeutil.FunctionSpec{
    75  				Container: runtimeutil.ContainerSpec{
    76  					Image: "gcr.io/kpt-fn/foo:bar",
    77  				},
    78  			},
    79  			expectedFnConfig: `
    80  metadata:
    81    name: function-input
    82  data: {a: b, c: d, e: f}
    83  kind: ConfigMap
    84  apiVersion: v1
    85  `,
    86  		},
    87  		{
    88  			name:   "config map dry-run",
    89  			args:   []string{"eval", dir, "--image", "foo:bar", "-o", "stdout", "--", "a=b", "c=d", "e=f"},
    90  			output: &bytes.Buffer{},
    91  			path:   dir,
    92  			expectedFn: &runtimeutil.FunctionSpec{
    93  				Container: runtimeutil.ContainerSpec{
    94  					Image: "gcr.io/kpt-fn/foo:bar",
    95  				},
    96  			},
    97  			expectedFnConfig: `
    98  metadata:
    99    name: function-input
   100  data: {a: b, c: d, e: f}
   101  kind: ConfigMap
   102  apiVersion: v1
   103  `,
   104  		},
   105  		{
   106  			name: "config map no args",
   107  			args: []string{"eval", dir, "--image", "foo:bar"},
   108  			path: dir,
   109  			expectedFn: &runtimeutil.FunctionSpec{
   110  				Container: runtimeutil.ContainerSpec{
   111  					Image: "gcr.io/kpt-fn/foo:bar",
   112  				},
   113  			},
   114  			expectedFnConfig: `
   115  metadata:
   116    name: function-input
   117  data: {}
   118  kind: ConfigMap
   119  apiVersion: v1
   120  `,
   121  		},
   122  		{
   123  			name:    "network enabled",
   124  			args:    []string{"eval", dir, "--image", "foo:bar", "--network"},
   125  			path:    dir,
   126  			network: true,
   127  			expectedFn: &runtimeutil.FunctionSpec{
   128  				Container: runtimeutil.ContainerSpec{
   129  					Image: "gcr.io/kpt-fn/foo:bar",
   130  				},
   131  			},
   132  			expectedFnConfig: `
   133  metadata:
   134    name: function-input
   135  data: {}
   136  kind: ConfigMap
   137  apiVersion: v1
   138  `,
   139  		},
   140  		{
   141  			name:    "with network name",
   142  			args:    []string{"eval", dir, "--image", "foo:bar", "--network"},
   143  			path:    dir,
   144  			network: true,
   145  			expectedFn: &runtimeutil.FunctionSpec{
   146  				Container: runtimeutil.ContainerSpec{
   147  					Image: "gcr.io/kpt-fn/foo:bar",
   148  				},
   149  			},
   150  			expectedFnConfig: `
   151  metadata:
   152    name: function-input
   153  data: {}
   154  kind: ConfigMap
   155  apiVersion: v1
   156  `,
   157  		},
   158  		{
   159  			name: "custom kind",
   160  			args: []string{"eval", dir, "--image", "foo:bar", "--", "Foo", "g=h"},
   161  			path: dir,
   162  			expectedFn: &runtimeutil.FunctionSpec{
   163  				Container: runtimeutil.ContainerSpec{
   164  					Image: "gcr.io/kpt-fn/foo:bar",
   165  				},
   166  			},
   167  			expectedFnConfig: `
   168  metadata:
   169    name: function-input
   170  data: {g: h}
   171  kind: Foo
   172  apiVersion: v1
   173  `,
   174  		},
   175  		{
   176  			name: "custom kind '=' in data",
   177  			args: []string{"eval", dir, "--image", "foo:bar", "--", "Foo", "g=h", "i=j=k"},
   178  			path: dir,
   179  			expectedFn: &runtimeutil.FunctionSpec{
   180  				Container: runtimeutil.ContainerSpec{
   181  					Image: "gcr.io/kpt-fn/foo:bar",
   182  				},
   183  			},
   184  			expectedFnConfig: `
   185  metadata:
   186    name: function-input
   187  data: {g: h, i: j=k}
   188  kind: Foo
   189  apiVersion: v1
   190  `,
   191  		},
   192  		{
   193  			name: "custom kind with storage mounts",
   194  			args: []string{
   195  				"eval", dir, "--mount", "type=bind,src=/mount/path,dst=/local/",
   196  				"--mount", "type=volume,src=myvol,dst=/local/",
   197  				"--mount", "type=tmpfs,dst=/local/",
   198  				"--image", "foo:bar", "--", "Foo", "g=h", "i=j=k"},
   199  			path:  dir,
   200  			mount: []string{"type=bind,src=/mount/path,dst=/local/", "type=volume,src=myvol,dst=/local/", "type=tmpfs,dst=/local/"},
   201  			expectedFn: &runtimeutil.FunctionSpec{
   202  				Container: runtimeutil.ContainerSpec{
   203  					Image: "gcr.io/kpt-fn/foo:bar",
   204  				},
   205  			},
   206  			expectedFnConfig: `
   207  metadata:
   208    name: function-input
   209  data: {g: h, i: j=k}
   210  kind: Foo
   211  apiVersion: v1
   212  `,
   213  		},
   214  		{
   215  			name: "results_dir",
   216  			args: []string{"eval", dir, "--results-dir", "foo/", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
   217  			path: dir,
   218  			expectedStruct: &runfn.RunFns{
   219  				Path:       dir,
   220  				ResultsDir: "foo/",
   221  				RunnerOptions: fnruntime.RunnerOptions{
   222  					ImagePullPolicy: fnruntime.IfNotPresentPull,
   223  				},
   224  				Env:                   []string{},
   225  				ContinueOnEmptyResult: true,
   226  				Ctx:                   context.TODO(),
   227  			},
   228  			expectedFn: &runtimeutil.FunctionSpec{
   229  				Container: runtimeutil.ContainerSpec{
   230  					Image: "gcr.io/kpt-fn/foo:bar",
   231  				},
   232  			},
   233  			expectedFnConfig: `
   234  metadata:
   235    name: function-input
   236  data: {a: b, c: d, e: f}
   237  kind: ConfigMap
   238  apiVersion: v1
   239  `,
   240  		},
   241  		{
   242  			name: "config map multi args",
   243  			args: []string{"eval", dir, "dir2", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
   244  			err:  "0 or 1 arguments supported",
   245  		},
   246  		{
   247  			name: "config map not image",
   248  			args: []string{"eval", dir, "--", "a=b", "c=d", "e=f"},
   249  			err:  "must specify --image",
   250  		},
   251  		{
   252  			name: "config map bad data",
   253  			args: []string{"eval", dir, "--image", "foo:bar", "--", "a=b", "c", "e=f"},
   254  			err:  "must have keys and values separated by",
   255  		},
   256  		{
   257  			name: "envs",
   258  			args: []string{"eval", dir, "--env", "FOO=BAR", "-e", "BAR", "--image", "foo:bar"},
   259  			path: dir,
   260  			expectedStruct: &runfn.RunFns{
   261  				Path: dir,
   262  				RunnerOptions: fnruntime.RunnerOptions{
   263  					ImagePullPolicy: fnruntime.IfNotPresentPull,
   264  				},
   265  				Env:                   []string{"FOO=BAR", "BAR"},
   266  				ContinueOnEmptyResult: true,
   267  				Ctx:                   context.TODO(),
   268  			},
   269  			expectedFn: &runtimeutil.FunctionSpec{
   270  				Container: runtimeutil.ContainerSpec{
   271  					Image: "gcr.io/kpt-fn/foo:bar",
   272  				},
   273  			},
   274  			expectedFnConfig: `
   275  metadata:
   276    name: function-input
   277  data: {}
   278  kind: ConfigMap
   279  apiVersion: v1
   280  `,
   281  		},
   282  		{
   283  			name: "as current user",
   284  			args: []string{"eval", dir, "--as-current-user", "--image", "foo:bar"},
   285  			path: dir,
   286  			expectedStruct: &runfn.RunFns{
   287  				Path:          dir,
   288  				AsCurrentUser: true,
   289  				RunnerOptions: fnruntime.RunnerOptions{
   290  					ImagePullPolicy: fnruntime.IfNotPresentPull,
   291  				},
   292  				Env:                   []string{},
   293  				ContinueOnEmptyResult: true,
   294  				Ctx:                   context.TODO(),
   295  			},
   296  			expectedFn: &runtimeutil.FunctionSpec{
   297  				Container: runtimeutil.ContainerSpec{
   298  					Image: "gcr.io/kpt-fn/foo:bar",
   299  				},
   300  			},
   301  			expectedFnConfig: `
   302  metadata:
   303    name: function-input
   304  data: {}
   305  kind: ConfigMap
   306  apiVersion: v1
   307  `,
   308  		},
   309  		{
   310  			name: "--fn-config flag",
   311  			args: []string{"eval", dir, "--fn-config", "a/b/c", "--image", "foo:bar"},
   312  			err:  "missing function config file: a/b/c",
   313  		},
   314  		{
   315  			name: "--fn-config with function arguments",
   316  			args: []string{"eval", dir, "--fn-config", "a/b/c", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
   317  			err:  "function arguments can only be specified without function config file",
   318  		},
   319  		{
   320  			name: "exec args",
   321  			args: []string{"eval", dir, "--exec", "execPath arg1 'arg2 arg3'", "--", "a=b", "c=d", "e=f"},
   322  			path: dir,
   323  			expectedFn: &runtimeutil.FunctionSpec{
   324  				Exec: runtimeutil.ExecSpec{
   325  					Path: "execPath",
   326  				},
   327  			},
   328  			expectedExecArgs: []string{"arg1", "arg2 arg3"},
   329  			expectedFnConfig: `
   330  metadata:
   331    name: function-input
   332  data: {a: b, c: d, e: f}
   333  kind: ConfigMap
   334  apiVersion: v1
   335  `,
   336  		},
   337  	}
   338  
   339  	for i := range tests {
   340  		tt := tests[i]
   341  		t.Run(tt.name, func(t *testing.T) {
   342  			r := GetEvalFnRunner(context.TODO(), "kpt")
   343  			// Don't run the actual command
   344  			r.Command.Run = nil
   345  			r.Command.RunE = func(cmd *cobra.Command, args []string) error { return nil }
   346  			r.Command.SilenceErrors = true
   347  			r.Command.SilenceUsage = true
   348  
   349  			// hack due to https://github.com/spf13/cobra/issues/42
   350  			root := &cobra.Command{Use: "root"}
   351  			root.AddCommand(r.Command)
   352  			root.SetArgs(tt.args)
   353  
   354  			// error case
   355  			err := r.Command.Execute()
   356  			if tt.err != "" {
   357  				if !assert.Error(t, err) {
   358  					t.FailNow()
   359  				}
   360  				if !assert.Contains(t, err.Error(), tt.err) {
   361  					t.FailNow()
   362  				}
   363  				// don't check anything else in error case
   364  				return
   365  			}
   366  
   367  			// non-error case
   368  			if !assert.NoError(t, err) {
   369  				t.FailNow()
   370  			}
   371  
   372  			// check if Input was set
   373  			if !assert.Equal(t, tt.input, r.runFns.Input) {
   374  				t.FailNow()
   375  			}
   376  
   377  			// check if Output was set
   378  			if !assert.Equal(t, tt.output, r.runFns.Output) {
   379  				t.FailNow()
   380  			}
   381  
   382  			// check if Path was set
   383  			if !assert.Equal(t, tt.path, r.runFns.Path) {
   384  				t.FailNow()
   385  			}
   386  
   387  			// check if Network was set
   388  			if tt.network {
   389  				if !assert.Equal(t, tt.network, r.runFns.Network) {
   390  					t.FailNow()
   391  				}
   392  			} else {
   393  				if !assert.Equal(t, false, r.runFns.Network) {
   394  					t.FailNow()
   395  				}
   396  			}
   397  
   398  			if !assert.Equal(t, tt.fnConfigPath, r.runFns.FnConfigPath) {
   399  				t.FailNow()
   400  			}
   401  
   402  			if !assert.Equal(t, toStorageMounts(tt.mount), r.runFns.StorageMounts) {
   403  				t.FailNow()
   404  			}
   405  
   406  			// check if function config was set
   407  			if tt.expectedFnConfig != "" {
   408  				actual := strings.TrimSpace(r.runFns.FnConfig.MustString())
   409  				if !assert.Equal(t, strings.TrimSpace(tt.expectedFnConfig), actual) {
   410  					t.FailNow()
   411  				}
   412  			}
   413  
   414  			// check if function was set
   415  			if tt.expectedFn != nil {
   416  				if !assert.NotNil(t, r.runFns.Function) {
   417  					t.FailNow()
   418  				}
   419  				if !assert.EqualValues(t, tt.expectedFn, r.runFns.Function) {
   420  					t.FailNow()
   421  				}
   422  			}
   423  
   424  			// check if exec arguments were set
   425  			if len(tt.expectedExecArgs) != 0 {
   426  				if !assert.EqualValues(t, tt.expectedExecArgs, r.runFns.ExecArgs) {
   427  					t.FailNow()
   428  				}
   429  			}
   430  
   431  			if tt.expectedStruct != nil {
   432  				r.runFns.Function = nil
   433  				r.runFns.FnConfig = nil
   434  				r.runFns.RunnerOptions.ResolveToImage = nil
   435  				tt.expectedStruct.FnConfigPath = tt.fnConfigPath
   436  				if !assert.Equal(t, *tt.expectedStruct, r.runFns) {
   437  					t.FailNow()
   438  				}
   439  			}
   440  		})
   441  	}
   442  }
   443  
   444  func TestCmd_flagAndArgParsing_Symlink(t *testing.T) {
   445  	dir, err := os.MkdirTemp("", "")
   446  	if !assert.NoError(t, err) {
   447  		t.FailNow()
   448  	}
   449  	defer os.RemoveAll(dir)
   450  	defer testutil.Chdir(t, dir)()
   451  
   452  	err = os.MkdirAll(filepath.Join(dir, "path", "to", "pkg", "dir"), 0700)
   453  	assert.NoError(t, err)
   454  	err = os.Symlink(filepath.Join("path", "to", "pkg", "dir"), "foo")
   455  	assert.NoError(t, err)
   456  
   457  	// verify the branch ref is set to the correct value
   458  	r := GetEvalFnRunner(fake.CtxWithDefaultPrinter(), "kpt")
   459  	r.Command.RunE = NoOpRunE
   460  	r.Command.SetArgs([]string{"foo", "-i", "bar:v0.1"})
   461  	err = r.Command.Execute()
   462  	assert.NoError(t, err)
   463  	assert.Equal(t, filepath.Join("path", "to", "pkg", "dir"), r.runFns.Path)
   464  }
   465  
   466  // NoOpRunE is a noop function to replace the run function of a command.  Useful for testing argument parsing.
   467  var NoOpRunE = func(cmd *cobra.Command, args []string) error { return nil }