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 }