diff --git a/cmd/finch/nerdctl.go b/cmd/finch/nerdctl.go index 18ab0fd48..15bd6dec5 100644 --- a/cmd/finch/nerdctl.go +++ b/cmd/finch/nerdctl.go @@ -16,6 +16,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/spf13/afero" "github.com/spf13/cobra" + orderedmap "github.com/wk8/go-ordered-map" "github.com/runfinch/finch/pkg/command" "github.com/runfinch/finch/pkg/config" @@ -108,12 +109,11 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error { return err } var ( - nerdctlArgs, envs, fileEnvs []string - skip, hasCmdHander, hasArgHandler bool - cmdHandler commandHandler - aMap map[string]argHandler - envArgPos int - isDebug int + nerdctlArgs, envs, fileEnvs, cmdArgs, runArgs []string + skip, hasCmdHander, hasArgHandler, lastOpt bool + cmdHandler commandHandler + aMap map[string]argHandler + firstOptPos int ) alias, hasAlias := aliasMap[cmdName] @@ -142,95 +142,188 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error { } } - // envArgPos is used to preserve the position of first environment parameter - envArgPos = -1 - // if a debug flag is passed before env arg pos we reduce the env arg pos by 1 to account for skipping debug flag - isDebug = 0 - for i, arg := range args { - // Check if command requires arg handling - if hasArgHandler { - // Check if argument for the command needs handling, sometimes it can be --file= - b, _, _ := strings.Cut(arg, "=") - h, ok := aMap[b] - if ok { - err = h(nc.systemDeps, args, i) - if err != nil { - return err - } - // This is required when the positional argument at i is mutated by argHandler, eg -v=C:\Users:/tmp:ro - arg = args[i] - } - } - // parsing environment values from the command line may pre-fetch and - // consume the next argument; this loop variable will skip these pre-consumed - // entries from the command line - if skip { - skip = false - continue - } + switch cmdName { + case "container run", "exec", "compose": + // check if an option flag is present; immediately following the command switch { - case arg == "--debug": - // explicitly setting log level to avoid `--debug` flag being interpreted as nerdctl command - if envArgPos == -1 { - isDebug = 1 - } - nc.logger.SetLevel(flog.Debug) - case argIsEnv(arg): - if envArgPos == -1 { - envArgPos = i - isDebug - } - shouldSkip, addEnv := handleEnv(nc.systemDeps, arg, args[i+1]) - skip = shouldSkip - if addEnv != "" { - envs = append(envs, addEnv) + case args[0] == "run" && strings.HasPrefix(args[1], "-"): + firstOptPos = 1 + case strings.HasPrefix(args[0], "-"): + firstOptPos = 0 + default: + firstOptPos = -1 + } + // lastOpt is used to track when no more flags need to be processed + lastOpt = false + + shortFlagBoolSet := cmdFlagSetMap[cmdName]["shortBoolFlags"] + longFlagBoolSet := cmdFlagSetMap[cmdName]["longBoolFlags"] + + shortFlagArgSet := cmdFlagSetMap[cmdName]["shortArgFlags"] + + for i, arg := range args { + // Check if command requires arg handling + if hasArgHandler { + // Check if argument for the command needs handling, sometimes it can be --file= + b, _, _ := strings.Cut(arg, "=") + h, ok := aMap[b] + if ok { + err = h(nc.systemDeps, args, i) + if err != nil { + return err + } + // This is required when the positional argument at i is mutated by argHandler, eg -v=C:\Users:/tmp:ro + arg = args[i] + } } - case strings.HasPrefix(arg, "--env-file"): - if envArgPos == -1 { - envArgPos = i - isDebug + // parsing arguments from the command line + // may pre-fetch and consume the next argument; + // the loop variable will skip any pre-consumed args + if skip { + skip = false + continue } - shouldSkip, addEnvs, err := handleEnvFile(nc.fs, nc.systemDeps, arg, args[i+1]) - if err != nil { - return err + // after last Option position is found, pass through each arg as an internal command argument (short-circuit) + if lastOpt { + cmdArgs = append(cmdArgs, arg) + continue } - skip = shouldSkip - fileEnvs = append(fileEnvs, addEnvs...) - case strings.HasPrefix(arg, "--add-host"): - switch arg { - case "--add-host": + + switch { + case arg == "--debug": + nc.logger.SetLevel(flog.Debug) + case arg == "--help": + nerdctlArgs = append(nerdctlArgs, arg) + case arg == "--add-host": + // exact match to --add-host args[i+1], err = resolveIP(args[i+1], nc.logger, nc.ecc) if err != nil { return err } - default: + nerdctlArgs = append(nerdctlArgs, arg) + case strings.HasPrefix(arg, "--add-host"): + // arg begins with --add-host resolvedIP, err := resolveIP(arg[11:], nc.logger, nc.ecc) if err != nil { return err } arg = fmt.Sprintf("%s%s", arg[0:11], resolvedIP) + nerdctlArgs = append(nerdctlArgs, arg) + case strings.HasPrefix(arg, "--env-file"): + // exact match to --env-file + // or arg begins with --env-file + shouldSkip, addEnvs, err := handleEnvFile(nc.fs, nc.systemDeps, arg, args[i+1]) + if err != nil { + return err + } + skip = shouldSkip + fileEnvs = append(fileEnvs, addEnvs...) + case argIsEnv(arg): + // exact match to either -e or --env + // or arg begins with -e or --env + // -e="", -e"" + // --env="=", --env"=" + shouldSkip, addEnv := handleEnv(nc.systemDeps, arg, args[i+1]) + skip = shouldSkip + if addEnv != "" { + envs = append(envs, addEnv) + } + case shortFlagBoolSet.Has(arg) || longFlagBoolSet.Has(arg): + // exact match to a short flag: -? + // or exact match to: -- + nerdctlArgs = append(nerdctlArgs, arg) + case shortFlagBoolSet.Has(arg[:2]): + // begins with a defined short flag, but is adjacent to one or more short flags: -???? + addArg := nc.handleMultipleShortFlags(shortFlagBoolSet, shortFlagArgSet, args, i) + nerdctlArgs = append(nerdctlArgs, addArg) + case shortFlagArgSet.Has(arg) || shortFlagArgSet.Has(arg[:2]): + // exact match to: -h,-m,-u,-w,-p,-l,-v + // or begins with: -h,-m,-u,-w,-p,-l,-v + // concatenated short flags and values: -p"8080:8080" + shouldSkip, addKey, addVal := nc.handleFlagArg(arg, args[i+1]) + skip = shouldSkip + if addKey != "" { + nerdctlArgs = append(nerdctlArgs, addKey) + nerdctlArgs = append(nerdctlArgs, addVal) + } + case strings.HasPrefix(arg, "--"): + // --="", -- "" + shouldSkip, addKey, addVal := nc.handleFlagArg(arg, args[i+1]) + skip = shouldSkip + if addKey != "" { + nerdctlArgs = append(nerdctlArgs, addKey) + nerdctlArgs = append(nerdctlArgs, addVal) + } + default: + // arg other than a flag ("-?","--") or a skipped + switch { + case (i < firstOptPos): + // arg is a value prior to the first found flag + // pass the arg as a nerdctl command argument + nerdctlArgs = append(nerdctlArgs, arg) + case (i >= firstOptPos) && !lastOpt: + // The first arg after procecssing the flags establishes the last Option Pos + lastOpt = true + cmdArgs = append(cmdArgs, arg) + default: + // Unexpected case + // pass the arg as a nerdctl arg by default + nc.logger.Debugln("Unexpected Arg Value: ", arg) + nerdctlArgs = append(nerdctlArgs, arg) + } } - nerdctlArgs = append(nerdctlArgs, arg) - if err != nil { - return err - } - default: - nerdctlArgs = append(nerdctlArgs, arg) } - } - // to handle environment variables properly, we add all entries found via - // env-file includes to the map first and then all command line environment - // flags, making sure that command line overrides environment file options, - // and that later command line flags override earlier ones - envVars := make(map[string]string) + // to handle environment variables properly, we add all entries found via + // env-file includes to the map first and then all command line environment + // flags, making sure that command line overrides environment file options, + // and that later command line flags override earlier ones + envVars := orderedmap.New() - for _, e := range fileEnvs { - evar, eval, _ := strings.Cut(e, "=") - envVars[evar] = eval - } - for _, e := range envs { - evar, eval, _ := strings.Cut(e, "=") - envVars[evar] = eval + for _, e := range fileEnvs { + evar, eval, _ := strings.Cut(e, "=") + envVars.Set(evar, eval) + } + for _, e := range envs { + evar, eval, _ := strings.Cut(e, "=") + envVars.Set(evar, eval) + } + + var envArgs []string + for pair := envVars.Oldest(); pair != nil; pair = pair.Next() { + envArgs = append(envArgs, "-e", fmt.Sprintf("%s=%s", pair.Key, pair.Value)) + } + + nerdctlArgs = append(nerdctlArgs, envArgs...) + nerdctlArgs = append(nerdctlArgs, cmdArgs...) + default: + + for i, arg := range args { + // Check if command requires arg handling + if hasArgHandler { + // Check if argument for the command needs handling, sometimes it can be --file= + b, _, _ := strings.Cut(arg, "=") + h, ok := aMap[b] + if ok { + err = h(nc.systemDeps, args, i) + if err != nil { + return err + } + // This is required when the positional argument at i is mutated by argHandler, eg -v=C:\Users:/tmp:ro + arg = args[i] + } + } + + switch { + case arg == "--debug": + nc.logger.SetLevel(flog.Debug) + case arg == "--help": + nerdctlArgs = append(nerdctlArgs, arg) + default: + nerdctlArgs = append(nerdctlArgs, arg) + } + } } passedEnvs := []string{ @@ -274,22 +367,16 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error { limaArgs = append(limaArgs, append([]string{nerdctlCmdName}, strings.Fields(cmdName)...)...) - var envArgs []string - for key, val := range envVars { - envArgs = append(envArgs, "-e", fmt.Sprintf("%s=%s", key, val)) - } - if envArgPos > -1 { - nerdctlArgs = append(nerdctlArgs[:envArgPos], append(envArgs, nerdctlArgs[envArgPos:]...)...) - } // Add -E to sudo command in order to preserve existing environment variables, more info: // https://stackoverflow.com/questions/8633461/how-to-keep-environment-variables-when-using-sudo/8633575#8633575 - limaArgs = append(limaArgs, nerdctlArgs...) + runArgs = append(runArgs, limaArgs...) + runArgs = append(runArgs, nerdctlArgs...) if nc.shouldReplaceForHelp(cmdName, args) { - return nc.lcc.RunWithReplacingStdout([]command.Replacement{{Source: "nerdctl", Target: "finch"}}, limaArgs...) + return nc.lcc.RunWithReplacingStdout([]command.Replacement{{Source: "nerdctl", Target: "finch"}}, runArgs...) } - return nc.lcc.Create(limaArgs...).Run() + return nc.lcc.Create(runArgs...).Run() } func (nc *nerdctlCommand) assertVMIsRunning(creator command.LimaCmdCreator, logger flog.Logger) error { @@ -336,10 +423,97 @@ func (nc *nerdctlCommand) shouldReplaceForHelp(cmdName string, args []string) bo } func argIsEnv(arg string) bool { - if strings.HasPrefix(arg, "-e") || (strings.HasPrefix(arg, "--env") && !strings.HasPrefix(arg, "--env-file")) { - return true + return strings.HasPrefix(arg, "-e") || (strings.HasPrefix(arg, "--env") && !strings.HasPrefix(arg, "--env-file")) +} + +func (nc *nerdctlCommand) handleMultipleShortFlags( + shortFlagBoolSet sets.Set[string], + shortFlagArgSet sets.Set[string], + args []string, + index int, +) string { + arg := args[index] + nextArg := args[index+1] + + argFlagFinal := false + argFlagMid := false + lastPos := len(arg) - 1 + + // Scan the compound shortFlag arg for details + for i, c := range strings.Split(arg, "") { + switch { + case c == "-": + continue + case i != lastPos && shortFlagArgSet.Has("-"+c): + argFlagMid = true + case i == lastPos && shortFlagArgSet.Has("-"+c): + argFlagFinal = true + default: + if !shortFlagBoolSet.Has("-" + c) { + nc.logger.Debugf("The group of short flags contains an unexpected value: %s, %s", arg, c) + } + } } - return false + + switch { + case argFlagMid: + // if a flag in the middle requires arguments, + // but there are more short flags immediately following + // then include a finch debug comment + // and pass the concatenated arg to nerdctl + nc.logger.Debugln("Mid Position Short Flag requires an Arg: ", arg) + case argFlagFinal: + // pre-pend the shortFlagArg member to the next Arg + separatedShortFlag := "-" + arg[lastPos:] + args[index+1] = separatedShortFlag + nextArg + // remove the shortFlagArg member from the current Arg + args[index] = arg[:lastPos] + arg = args[index] + default: + // if every following character is a shortFlagBool + // then pass into nerdctlArgs + } + + return arg +} + +func (nc *nerdctlCommand) handleFlagArg(arg string, nextArg string) (bool, string, string) { + // handling Flag arguments other than environment variables + // note: a Bool Flag should not be passed into this helper function; only Flags that are followed by one argument + var ( + flagKey, flagVal string + skip bool + ) + switch { + case strings.HasPrefix(arg, "--") && strings.Contains(arg, "="): + // long flag concatenated to value by '=': --="" + skip = false + flagKey, flagVal, _ = strings.Cut(arg, "=") + case strings.HasPrefix(arg, "--") && !strings.HasPrefix(nextArg, "-"): + // long flag followed by a value: -- "" + skip = true + flagKey = arg + flagVal = nextArg + // undefined case: long flag adjacent to value: --"" or -- + case strings.HasPrefix(arg, "-") && strings.Contains(arg, "="): + // short flag concatenated to value by '=': -?="" or -?= + skip = false + flagKey, flagVal, _ = strings.Cut(arg, "=") + case strings.HasPrefix(arg, "-") && len(arg) > 2: + // short flag adjacent to value: -?"" or -? + skip = false + flagKey = arg[:2] + flagVal = arg[2:] + case strings.HasPrefix(arg, "-") && len(arg) == 2 && !strings.HasPrefix(nextArg, "-"): + // short flag followed by a value: -? "" or -? + skip = true + flagKey = arg + flagVal = nextArg + default: + return false, "", "" + } + + return skip, flagKey, flagVal } func handleEnv(systemDeps NerdctlCommandSystemDeps, arg, arg2 string) (bool, string) { @@ -357,7 +531,8 @@ func handleEnv(systemDeps NerdctlCommandSystemDeps, arg, arg2 string) (bool, str envVar = arg[2:] } else { // only other case is "--env="; skip that prefix - envVar = arg[6:] + eqPos := strings.Index(arg, "=") + envVar = arg[eqPos+1:] } } @@ -501,3 +676,27 @@ var nerdctlCmds = map[string]string{ "volume": "Manage volumes", "wait": "Block until one or more containers stop, then print their exit codes", } + +var cmdFlagSetMap = map[string]map[string]sets.Set[string]{ + "container run": { + "shortBoolFlags": sets.New[string]("-d", "-i", "-t"), + "longBoolFlags": sets.New[string]( + "--detach", "--init", "--interactive", "--oom-kill-disable", + "--privileged", "--read-only", "--rm", "--rootfs", "--tty"), + "shortArgFlags": sets.New[string]("-e", "-h", "-m", "-u", "-w", "-p", "-l", "-v"), + }, + "exec": { + "shortBoolFlags": sets.New[string]("-d", "-i", "-t"), + "longBoolFlags": sets.New[string]( + "--detach", "--init", "--interactive", "--oom-kill-disable", + "--privileged", "--read-only", "--rm", "--rootfs", "--tty"), + "shortArgFlags": sets.New[string]("-e", "-h", "-m", "-u", "-w", "-p", "-l", "-v"), + }, + "compose": { + "shortBoolFlags": sets.New[string]("-d", "-i", "-t"), + "longBoolFlags": sets.New[string]( + "--detach", "--init", "--interactive", "--oom-kill-disable", + "--privileged", "--read-only", "--rm", "--rootfs", "--tty"), + "shortArgFlags": sets.New[string]("-e", "-h", "-m", "-u", "-w", "-p", "-l", "-v"), + }, +} diff --git a/cmd/finch/nerdctl_darwin.go b/cmd/finch/nerdctl_darwin.go index f6a9f4d02..14cf4e212 100644 --- a/cmd/finch/nerdctl_darwin.go +++ b/cmd/finch/nerdctl_darwin.go @@ -20,7 +20,9 @@ func convertToWSLPath(_ NerdctlCommandSystemDeps, _ string) (string, error) { return "", nil } -var aliasMap = map[string]string{} +var aliasMap = map[string]string{ + "run": "container run", +} var argHandlerMap = map[string]map[string]argHandler{} diff --git a/cmd/finch/nerdctl_darwin_test.go b/cmd/finch/nerdctl_darwin_test.go index 36671d2ad..82f1724d6 100644 --- a/cmd/finch/nerdctl_darwin_test.go +++ b/cmd/finch/nerdctl_darwin_test.go @@ -73,9 +73,8 @@ func TestNerdctlCommand_runAdaptor(t *testing.T) { } } -func TestNerdctlCommand_run(t *testing.T) { +func TestNerdctlCommand_run_pullCommand(t *testing.T) { t.Parallel() - envFilePath := filepath.Join(string(filepath.Separator), "env-file") testCases := []struct { name string cmdName string @@ -93,10 +92,10 @@ func TestNerdctlCommand_run(t *testing.T) { ) }{ { - name: "happy path", - cmdName: "build", + name: "with --debug flag", + cmdName: "pull", fc: &config.Finch{}, - args: []string{"-t", "demo", "."}, + args: []string{"test:tag", "--debug"}, wantErr: nil, mockSvc: func( _ *testing.T, @@ -114,18 +113,19 @@ func TestNerdctlCommand_run(t *testing.T) { ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) + logger.EXPECT().SetLevel(flog.Debug) ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "build", "-t", "demo", ".").Return(c) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "pull", "test:tag").Return(c) c.EXPECT().Run() }, }, { - name: "with --debug flag", + name: "with --help flag", cmdName: "pull", fc: &config.Finch{}, - args: []string{"test:tag", "--debug"}, + args: []string{"test:tag", "--help"}, wantErr: nil, mockSvc: func( _ *testing.T, @@ -140,23 +140,21 @@ func TestNerdctlCommand_run(t *testing.T) { lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) - ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) - logger.EXPECT().SetLevel(flog.Debug) ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) - ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) - c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "pull", "test:tag").Return(c) - c.EXPECT().Run() + lcc.EXPECT().RunWithReplacingStdout( + testStdoutRs, "shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "pull", "test:tag", "--help").Return(nil) }, }, { - name: "with environment flags parsing and env value doesn't exist", - cmdName: "run", + name: "with --help flag but replacing returns error", + cmdName: "pull", fc: &config.Finch{}, - args: []string{"--rm", "-e", "ARG1=val1", "--env=ARG2", "-eARG3", "alpine:latest", "env"}, - wantErr: nil, + args: []string{"test:tag", "--help"}, + wantErr: fmt.Errorf("failed to replace"), mockSvc: func( _ *testing.T, lcc *mocks.LimaCmdCreator, @@ -170,25 +168,21 @@ func TestNerdctlCommand_run(t *testing.T) { lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) - ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) - c := mocks.NewCommand(ctrl) - ncsd.EXPECT().LookupEnv("ARG2") - ncsd.EXPECT().LookupEnv("ARG3") ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) - ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) - - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "run", - "--rm", "-e", "ARG1=val1", "alpine:latest", "env").Return(c) - c.EXPECT().Run() + lcc.EXPECT().RunWithReplacingStdout( + testStdoutRs, "shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "pull", "test:tag", "--help"). + Return(fmt.Errorf("failed to replace")) }, }, { - name: "with environment flags parsing and env value exists", - cmdName: "run", + name: "with COSIGN_PASSWORD env var and --verify=cosign", + cmdName: "pull", fc: &config.Finch{}, - args: []string{"--rm", "--env=ARG2", "-eARG3", "alpine:latest", "env"}, + args: []string{"--verify=cosign", "test:tag"}, wantErr: nil, mockSvc: func( _ *testing.T, @@ -206,21 +200,19 @@ func TestNerdctlCommand_run(t *testing.T) { ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) - c := mocks.NewCommand(ctrl) - ncsd.EXPECT().LookupEnv("ARG2") - ncsd.EXPECT().LookupEnv("ARG3").Return("val3", true) - ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) + ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("test", true) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "run", - "--rm", "-e", "ARG3=val3", "alpine:latest", "env").Return(c) + c := mocks.NewCommand(ctrl) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", "COSIGN_PASSWORD=test", nerdctlCmdName, + "pull", "--verify=cosign", "test:tag").Return(c) c.EXPECT().Run() }, }, { - name: "with environment flags parsing and env value exists and with --debug flag", - cmdName: "run", + name: "with COSIGN_PASSWORD env var without cosign arg", + cmdName: "pull", fc: &config.Finch{}, - args: []string{"--debug", "--rm", "--env=ARG2", "-eARG3", "alpine:latest", "env"}, + args: []string{"test:tag"}, wantErr: nil, mockSvc: func( _ *testing.T, @@ -234,140 +226,201 @@ func TestNerdctlCommand_run(t *testing.T) { getVMStatusC := mocks.NewCommand(ctrl) lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) - logger.EXPECT().SetLevel(flog.Debug) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) - c := mocks.NewCommand(ctrl) - ncsd.EXPECT().LookupEnv("ARG2") - ncsd.EXPECT().LookupEnv("ARG3").Return("val3", true) - ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) + ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("test", true) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "run", - "--rm", "-e", "ARG3=val3", "alpine:latest", "env").Return(c) + c := mocks.NewCommand(ctrl) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", "COSIGN_PASSWORD=test", + nerdctlCmdName, "pull", "test:tag").Return(c) c.EXPECT().Run() }, }, { - name: "with --env-file flag replacement", - cmdName: "run", - fc: &config.Finch{}, - args: []string{"--rm", "--env-file=/env-file", "alpine:latest", "env"}, + name: "with ECR credential helper and environment set", + cmdName: "pull", + fc: &config.Finch{ + CredsHelpers: []string{"ecr-login"}, + }, + args: []string{"test:tag"}, wantErr: nil, mockSvc: func( - t *testing.T, + _ *testing.T, lcc *mocks.LimaCmdCreator, - _ *mocks.CommandCreator, + ecc *mocks.CommandCreator, ncsd *mocks.NerdctlCommandSystemDeps, logger *mocks.Logger, ctrl *gomock.Controller, - fs afero.Fs, + _ afero.Fs, ) { - envFileStr := "# a comment\nARG1=val1\n ARG2\n\n # a 2nd comment\nNOTSETARG\n " - require.NoError(t, afero.WriteFile(fs, envFilePath, []byte(envFileStr), 0o600)) - getVMStatusC := mocks.NewCommand(ctrl) lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") - ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) - ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) - ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) - c := mocks.NewCommand(ctrl) - ncsd.EXPECT().LookupEnv("ARG2") - ncsd.EXPECT().LookupEnv("NOTSETARG") + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("TEST_ACCESS_KEY", true) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("TEST_SECRET_ACCESS_KEY", true) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("TEST_SESSION_TOKEN", true) ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) - lcc.EXPECT(). - Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "run", "--rm", "-e", "ARG1=val1", "alpine:latest", "env"). - Return(c) + + awsCmd := mocks.NewCommand(ctrl) + ecc.EXPECT().Create( + "aws", + "configure", + "export-credentials", + "--format", + "process", + ).Return(awsCmd) + awsCmd.EXPECT().CombinedOutput().Return([]byte(`{ + "AccessKeyID": "TEST_ACCESS_KEY_FROM_PROCESS", + "SecretAccessKey": "TEST_SECRET_ACCESS_KEY_FROM_PROCESS", + "SessionToken": "TEST_SESSION_TOKEN_FROM_PROCESS" +} +`), nil) + + c := mocks.NewCommand(ctrl) + lcc.EXPECT().Create("shell", + limaInstanceName, + "sudo", + "-E", + "AWS_ACCESS_KEY_ID=TEST_ACCESS_KEY_FROM_PROCESS", + "AWS_SECRET_ACCESS_KEY=TEST_SECRET_ACCESS_KEY_FROM_PROCESS", + "AWS_SESSION_TOKEN=TEST_SESSION_TOKEN_FROM_PROCESS", + "AWS_ACCESS_KEY_ID=TEST_ACCESS_KEY", + "AWS_SECRET_ACCESS_KEY=TEST_SECRET_ACCESS_KEY", + "AWS_SESSION_TOKEN=TEST_SESSION_TOKEN", + nerdctlCmdName, + "pull", + "test:tag", + ).Return(c) c.EXPECT().Run() }, }, { - name: "with --env-file flag replacement and with --debug flag", - cmdName: "run", - fc: &config.Finch{}, - args: []string{"--debug", "--rm", "--env-file=/env-file", "alpine:latest", "env"}, + name: "with ECR credential helper, no environment set", + cmdName: "pull", + fc: &config.Finch{ + CredsHelpers: []string{"ecr-login"}, + }, + args: []string{"test:tag"}, wantErr: nil, mockSvc: func( - t *testing.T, + _ *testing.T, lcc *mocks.LimaCmdCreator, - _ *mocks.CommandCreator, + ecc *mocks.CommandCreator, ncsd *mocks.NerdctlCommandSystemDeps, logger *mocks.Logger, ctrl *gomock.Controller, - fs afero.Fs, + _ afero.Fs, ) { - envFileStr := "# a comment\nARG1=val1\n ARG2\n\n # a 2nd comment\nNOTSETARG\n " - require.NoError(t, afero.WriteFile(fs, envFilePath, []byte(envFileStr), 0o600)) - getVMStatusC := mocks.NewCommand(ctrl) lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) - logger.EXPECT().SetLevel(flog.Debug) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") - ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) - ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) - ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) - c := mocks.NewCommand(ctrl) - ncsd.EXPECT().LookupEnv("ARG2") - ncsd.EXPECT().LookupEnv("NOTSETARG") + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("TEST_ACCESS_KEY", false) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("TEST_SECRET_ACCESS_KEY", false) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("TEST_SESSION_TOKEN", false) ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) - lcc.EXPECT(). - Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "run", "--rm", "-e", "ARG1=val1", "alpine:latest", "env"). - Return(c) + + awsCmd := mocks.NewCommand(ctrl) + ecc.EXPECT().Create( + "aws", + "configure", + "export-credentials", + "--format", + "process", + ).Return(awsCmd) + awsCmd.EXPECT().CombinedOutput().Return([]byte(`{ + "AccessKeyID": "TEST_ACCESS_KEY_FROM_PROCESS", + "SecretAccessKey": "TEST_SECRET_ACCESS_KEY_FROM_PROCESS", + "SessionToken": "TEST_SESSION_TOKEN_FROM_PROCESS" +} +`), nil) + + c := mocks.NewCommand(ctrl) + lcc.EXPECT().Create("shell", + limaInstanceName, + "sudo", + "-E", + "AWS_ACCESS_KEY_ID=TEST_ACCESS_KEY_FROM_PROCESS", + "AWS_SECRET_ACCESS_KEY=TEST_SECRET_ACCESS_KEY_FROM_PROCESS", + "AWS_SESSION_TOKEN=TEST_SESSION_TOKEN_FROM_PROCESS", + nerdctlCmdName, + "pull", + "test:tag", + ).Return(c) c.EXPECT().Run() }, }, { - name: "with --env-file flag replacement and existing env value", - cmdName: "run", - fc: &config.Finch{}, - args: []string{"--rm", "--env-file", envFilePath, "alpine:latest", "env"}, + name: "with ECR credential helper, aws command fails but environment is used", + cmdName: "pull", + fc: &config.Finch{ + CredsHelpers: []string{"ecr-login"}, + }, + args: []string{"test:tag"}, wantErr: nil, mockSvc: func( - t *testing.T, + _ *testing.T, lcc *mocks.LimaCmdCreator, - _ *mocks.CommandCreator, + ecc *mocks.CommandCreator, ncsd *mocks.NerdctlCommandSystemDeps, logger *mocks.Logger, ctrl *gomock.Controller, - fs afero.Fs, + _ afero.Fs, ) { - envFileStr := "# a comment\n ARG2\n\n # a 2nd comment\nNOTSETARG\n " - require.NoError(t, afero.WriteFile(fs, envFilePath, []byte(envFileStr), 0o600)) - getVMStatusC := mocks.NewCommand(ctrl) lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") - ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) - ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) - ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) - c := mocks.NewCommand(ctrl) - ncsd.EXPECT().LookupEnv("ARG2").Return("val2", true) - ncsd.EXPECT().LookupEnv("NOTSETARG") + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("TEST_ACCESS_KEY", true) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("TEST_SECRET_ACCESS_KEY", true) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("TEST_SESSION_TOKEN", true) ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) - lcc.EXPECT(). - Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "run", "--rm", "-e", "ARG2=val2", "alpine:latest", "env"). - Return(c) + + awsCmd := mocks.NewCommand(ctrl) + ecc.EXPECT().Create( + "aws", + "configure", + "export-credentials", + "--format", + "process", + ).Return(awsCmd) + awsCmd.EXPECT().CombinedOutput().Return(nil, fmt.Errorf("an error")) + logger.EXPECT().Debugln("failed to run `aws configure` command") + + c := mocks.NewCommand(ctrl) + lcc.EXPECT().Create("shell", + limaInstanceName, + "sudo", + "-E", + "AWS_ACCESS_KEY_ID=TEST_ACCESS_KEY", + "AWS_SECRET_ACCESS_KEY=TEST_SECRET_ACCESS_KEY", + "AWS_SESSION_TOKEN=TEST_SESSION_TOKEN", + nerdctlCmdName, + "pull", + "test:tag", + ).Return(c) c.EXPECT().Run() }, }, { - name: "with --env-file flag, but the specified file does not exist", - cmdName: "run", - args: []string{"--rm", "--env-file", envFilePath, "alpine:latest", "env"}, - wantErr: &os.PathError{Op: "open", Path: envFilePath, Err: afero.ErrFileNotFound}, + name: "with ECR credential helper, aws command fails but returns unexpected response", + cmdName: "pull", + fc: &config.Finch{ + CredsHelpers: []string{"ecr-login"}, + }, + args: []string{"test:tag"}, + wantErr: nil, mockSvc: func( _ *testing.T, lcc *mocks.LimaCmdCreator, - _ *mocks.CommandCreator, - _ *mocks.NerdctlCommandSystemDeps, + ecc *mocks.CommandCreator, + ncsd *mocks.NerdctlCommandSystemDeps, logger *mocks.Logger, ctrl *gomock.Controller, _ afero.Fs, @@ -376,13 +429,85 @@ func TestNerdctlCommand_run(t *testing.T) { lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") - }, + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("TEST_ACCESS_KEY", true) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("TEST_SECRET_ACCESS_KEY", true) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("TEST_SESSION_TOKEN", true) + ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) + ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) + + awsCmd := mocks.NewCommand(ctrl) + ecc.EXPECT().Create( + "aws", + "configure", + "export-credentials", + "--format", + "process", + ).Return(awsCmd) + awsCmd.EXPECT().CombinedOutput().Return([]byte("unexpected response"), nil) + logger.EXPECT().Debugln("`aws configure export-credentials` output is unexpected, is command available? " + + "This may result in a broken ecr-credential helper experience.") + + c := mocks.NewCommand(ctrl) + lcc.EXPECT().Create("shell", + limaInstanceName, + "sudo", + "-E", + "AWS_ACCESS_KEY_ID=TEST_ACCESS_KEY", + "AWS_SECRET_ACCESS_KEY=TEST_SECRET_ACCESS_KEY", + "AWS_SESSION_TOKEN=TEST_SESSION_TOKEN", + nerdctlCmdName, + "pull", + "test:tag", + ).Return(c) + c.EXPECT().Run() + }, }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + lcc := mocks.NewLimaCmdCreator(ctrl) + ecc := mocks.NewCommandCreator(ctrl) + ncsd := mocks.NewNerdctlCommandSystemDeps(ctrl) + logger := mocks.NewLogger(ctrl) + fs := afero.NewMemMapFs() + tc.mockSvc(t, lcc, ecc, ncsd, logger, ctrl, fs) + + assert.Equal(t, tc.wantErr, newNerdctlCommand(lcc, ecc, ncsd, logger, fs, tc.fc).run(tc.cmdName, tc.args)) + }) + } +} + +func TestNerdctlCommand_run(t *testing.T) { + t.Parallel() + envFilePath := filepath.Join(string(filepath.Separator), "env-file") + testCases := []struct { + name string + cmdName string + fc *config.Finch + args []string + wantErr error + mockSvc func( + t *testing.T, + lcc *mocks.LimaCmdCreator, + ecc *mocks.CommandCreator, + ncsd *mocks.NerdctlCommandSystemDeps, + logger *mocks.Logger, + ctrl *gomock.Controller, + fs afero.Fs, + ) + }{ { - name: "with --add-host flag and special IP by space", + name: "with single option flag", cmdName: "run", fc: &config.Finch{}, - args: []string{"--rm", "--add-host", "name:host-gateway", "alpine:latest"}, + args: []string{ + "-it", "alpine:latest", "env", + }, wantErr: nil, mockSvc: func( _ *testing.T, @@ -400,20 +525,22 @@ func TestNerdctlCommand_run(t *testing.T) { ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) - logger.EXPECT().Debugf(`Resolving special IP "host-gateway" to %q for host %q`, "192.168.5.2", "name") ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "run", - "--rm", "--add-host", "name:192.168.5.2", "alpine:latest").Return(c) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "-it", "alpine:latest", "env").Return(c) c.EXPECT().Run() }, }, { - name: "with --add-host flag but without using special IP by space", + name: "with explicit env flag parsing", cmdName: "run", fc: &config.Finch{}, - args: []string{"--rm", "--add-host", "name:0.0.0.0", "alpine:latest"}, + args: []string{ + "-it", "-e", "ARG1=val1", "--env=ARG2=val2", "-eARG3=val3", + "--name", "myContainer", "--rm", "alpine:latest", "env", + }, wantErr: nil, mockSvc: func( _ *testing.T, @@ -434,17 +561,18 @@ func TestNerdctlCommand_run(t *testing.T) { ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "run", - "--rm", "--add-host", "name:0.0.0.0", "alpine:latest").Return(c) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "-it", "--name", "myContainer", "--rm", "-e", "ARG1=val1", "-e", "ARG2=val2", "-e", "ARG3=val3", + "alpine:latest", "env").Return(c) c.EXPECT().Run() }, }, { - name: "with --add-host flag but without subsequent arg", + name: "with implicit env flag parsing; values exist in host env", cmdName: "run", fc: &config.Finch{}, - args: []string{"--rm", "--add-host", "alpine:latest"}, - wantErr: errors.New("run cmd error"), + args: []string{"-it", "-e", "ARG1", "--env=ARG2", "-eARG3", "--rm", "--name", "myContainer", "alpine:latest", "env"}, + wantErr: nil, mockSvc: func( _ *testing.T, lcc *mocks.LimaCmdCreator, @@ -461,19 +589,26 @@ func TestNerdctlCommand_run(t *testing.T) { ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) + ncsd.EXPECT().LookupEnv("ARG1").Return("val1", true) + ncsd.EXPECT().LookupEnv("ARG2").Return("val2", true) + ncsd.EXPECT().LookupEnv("ARG3").Return("val3", true) ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "run", - "--rm", "--add-host", "alpine:latest").Return(c) - c.EXPECT().Run().Return(errors.New("run cmd error")) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "-it", "--rm", "--name", "myContainer", "-e", "ARG1=val1", "-e", "ARG2=val2", "-e", "ARG3=val3", + "alpine:latest", "env").Return(c) + c.EXPECT().Run() }, }, { - name: "with --add-host flag and special IP by equal", + name: "with implicit env flag parsing; values do not exist", cmdName: "run", fc: &config.Finch{}, - args: []string{"--rm", "--add-host=name:host-gateway", "alpine:latest"}, + args: []string{ + "--name", "myContainer", "-it", "-e", "ARG0=val0", "-e", "ARG1", "--env=ARG2", "-eARG3", + "--rm", "alpine:latest", "env", + }, wantErr: nil, mockSvc: func( _ *testing.T, @@ -491,20 +626,25 @@ func TestNerdctlCommand_run(t *testing.T) { ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) - logger.EXPECT().Debugf(`Resolving special IP "host-gateway" to %q for host %q`, "192.168.5.2", "name") + ncsd.EXPECT().LookupEnv("ARG1") + ncsd.EXPECT().LookupEnv("ARG2") + ncsd.EXPECT().LookupEnv("ARG3") ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "run", - "--rm", "--add-host=name:192.168.5.2", "alpine:latest").Return(c) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "--name", "myContainer", "-it", "--rm", "-e", "ARG0=val0", "alpine:latest", "env").Return(c) c.EXPECT().Run() }, }, { - name: "with --add-host flag but without using special IP by equal", + name: "with explicit env flag parsing and debug mode", cmdName: "run", fc: &config.Finch{}, - args: []string{"--rm", "--add-host=name:0.0.0.0", "alpine:latest"}, + args: []string{ + "--debug", "--name", "myContainer", "--rm", "-e", "ARG1=val1", "--env=ARG2=val2", + "-it", "-eARG3=val3", "alpine:latest", "env", + }, wantErr: nil, mockSvc: func( _ *testing.T, @@ -518,27 +658,25 @@ func TestNerdctlCommand_run(t *testing.T) { getVMStatusC := mocks.NewCommand(ctrl) lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().SetLevel(flog.Debug) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) - c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "run", - "--rm", "--add-host=name:0.0.0.0", "alpine:latest").Return(c) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "--name", "myContainer", "--rm", "-it", "-e", "ARG1=val1", "-e", "ARG2=val2", "-e", "ARG3=val3", + "alpine:latest", "env").Return(c) c.EXPECT().Run() }, }, { - name: "with multiple nested volumes", + name: "with implicit env flag parsing and debug mode; values exist in host env", cmdName: "run", fc: &config.Finch{}, - args: []string{ - "--rm", "-v", "/tmp:/tmp1/tmp2:rro", "--volume", "/tmp:/tmp1:rprivate,rro", "-v=/tmp:/tmp1/tmp2/tmp3/tmp4:rro", - "--volume=/tmp:/tmp1/tmp3/tmp4:rshared", "-v", "volume", "alpine:latest", - }, + args: []string{"--debug", "--rm", "--name", "myContainer", "-e", "ARG1", "--env=ARG2", "-it", "-eARG3", "alpine:latest", "env"}, wantErr: nil, mockSvc: func( _ *testing.T, @@ -552,26 +690,30 @@ func TestNerdctlCommand_run(t *testing.T) { getVMStatusC := mocks.NewCommand(ctrl) lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().SetLevel(flog.Debug) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) + ncsd.EXPECT().LookupEnv("ARG1").Return("val1", true) + ncsd.EXPECT().LookupEnv("ARG2").Return("val2", true) + ncsd.EXPECT().LookupEnv("ARG3").Return("val3", true) ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "run", - "--rm", "-v", "/tmp:/tmp1/tmp2:rro", "--volume", "/tmp:/tmp1:rprivate,rro", "-v=/tmp:/tmp1/tmp2/tmp3/tmp4:rro", - "--volume=/tmp:/tmp1/tmp3/tmp4:rshared", "-v", "volume", "alpine:latest").Return(c) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "--rm", "--name", "myContainer", "-it", "-e", "ARG1=val1", "-e", "ARG2=val2", "-e", "ARG3=val3", + "alpine:latest", "env").Return(c) c.EXPECT().Run() }, }, { - name: "with multiple nested volumes with full container run command", - cmdName: "container", + name: "with implicit env flag parsing and debug mode; values do not exist", + cmdName: "run", fc: &config.Finch{}, args: []string{ - "run", "--rm", "-v", "/tmp:/tmp1/tmp2:rro", "--volume", "/tmp:/tmp1:rprivate,rro", - "-v=/tmp:/tmp1/tmp2/tmp3/tmp4:rro", "--volume=/tmp:/tmp1/tmp3/tmp4:rshared", "-v", "volume", "alpine:latest", + "--debug", "--rm", "-e", "ARG0=val0", "-e", "ARG1", "--env=ARG2", "-it", + "--name", "myContainer", "-eARG3", "alpine:latest", "env", }, wantErr: nil, mockSvc: func( @@ -586,24 +728,30 @@ func TestNerdctlCommand_run(t *testing.T) { getVMStatusC := mocks.NewCommand(ctrl) lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().SetLevel(flog.Debug) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) + ncsd.EXPECT().LookupEnv("ARG1") + ncsd.EXPECT().LookupEnv("ARG2") + ncsd.EXPECT().LookupEnv("ARG3") ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) c := mocks.NewCommand(ctrl) lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", - "--rm", "-v", "/tmp:/tmp1/tmp2:rro", "--volume", "/tmp:/tmp1:rprivate,rro", - "-v=/tmp:/tmp1/tmp2/tmp3/tmp4:rro", "--volume=/tmp:/tmp1/tmp3/tmp4:rshared", "-v", "volume", "alpine:latest").Return(c) + "--rm", "-it", "--name", "myContainer", "-e", "ARG0=val0", "alpine:latest", "env").Return(c) c.EXPECT().Run() }, }, { - name: "with --help flag", - cmdName: "pull", + name: "with explicit env flag parsing and image args", + cmdName: "run", fc: &config.Finch{}, - args: []string{"test:tag", "--help"}, + args: []string{ + "--debug", "-i", "--name", "myContainer", "--rm", "-e", "ARG1=val1", "--env=ARG2=val2", + "-t", "-eARG3=val3", "busybox:latest", "echo", "-e", "hello\tbye", + }, wantErr: nil, mockSvc: func( _ *testing.T, @@ -617,31 +765,38 @@ func TestNerdctlCommand_run(t *testing.T) { getVMStatusC := mocks.NewCommand(ctrl) lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().SetLevel(flog.Debug) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) - lcc.EXPECT().RunWithReplacingStdout( - testStdoutRs, "shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "pull", "test:tag", "--help").Return(nil) + c := mocks.NewCommand(ctrl) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "-i", "--name", "myContainer", "--rm", "-t", "-e", "ARG1=val1", "-e", "ARG2=val2", "-e", "ARG3=val3", + "busybox:latest", "echo", "-e", "hello\tbye").Return(c) + c.EXPECT().Run() }, }, { - name: "with --help flag but replacing returns error", - cmdName: "pull", + name: "with --env-file flag replacement", + cmdName: "run", fc: &config.Finch{}, - args: []string{"test:tag", "--help"}, - wantErr: fmt.Errorf("failed to replace"), + args: []string{"-i", "--name", "myContainer", "--rm", "--env-file=/env-file", "alpine:latest", "env"}, + wantErr: nil, mockSvc: func( - _ *testing.T, + t *testing.T, lcc *mocks.LimaCmdCreator, _ *mocks.CommandCreator, ncsd *mocks.NerdctlCommandSystemDeps, logger *mocks.Logger, ctrl *gomock.Controller, - _ afero.Fs, + fs afero.Fs, ) { + envFileStr := "# a comment\nARG1=val1\n ARG2\n\n # a 2nd comment\nNOTSETARG\n " + require.NoError(t, afero.WriteFile(fs, envFilePath, []byte(envFileStr), 0o600)) + getVMStatusC := mocks.NewCommand(ctrl) lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) @@ -649,58 +804,76 @@ func TestNerdctlCommand_run(t *testing.T) { ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) + c := mocks.NewCommand(ctrl) + ncsd.EXPECT().LookupEnv("ARG2") + ncsd.EXPECT().LookupEnv("NOTSETARG") ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) - lcc.EXPECT().RunWithReplacingStdout( - testStdoutRs, "shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "pull", "test:tag", "--help"). - Return(fmt.Errorf("failed to replace")) + lcc.EXPECT(). + Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "-i", "--name", "myContainer", "--rm", "-e", "ARG1=val1", + "alpine:latest", "env").Return(c) + c.EXPECT().Run() }, }, { - name: "with COSIGN_PASSWORD env var and --sign=cosign", - cmdName: "push", + name: "with --env-file flag replacement and with --debug flag; implicit value not present", + cmdName: "run", fc: &config.Finch{}, - args: []string{"--sign=cosign", "test:tag"}, + args: []string{"--debug", "--rm", "--env-file=/env-file", "alpine:latest", "env"}, wantErr: nil, mockSvc: func( - _ *testing.T, + t *testing.T, lcc *mocks.LimaCmdCreator, _ *mocks.CommandCreator, ncsd *mocks.NerdctlCommandSystemDeps, logger *mocks.Logger, ctrl *gomock.Controller, - _ afero.Fs, + fs afero.Fs, ) { + envFileStr := "# a comment\nARG1=val1\n ARG2\n\n # a 2nd comment\nNOTSETARG\n " + require.NoError(t, afero.WriteFile(fs, envFilePath, []byte(envFileStr), 0o600)) + getVMStatusC := mocks.NewCommand(ctrl) lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().SetLevel(flog.Debug) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) - ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("test", true) - ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", "COSIGN_PASSWORD=test", nerdctlCmdName, - "push", "--sign=cosign", "test:tag").Return(c) + ncsd.EXPECT().LookupEnv("ARG2") + ncsd.EXPECT().LookupEnv("NOTSETARG") + ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) + ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) + lcc.EXPECT(). + Create( + "shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", "--rm", + "-e", "ARG1=val1", "alpine:latest", "env", + ). + Return(c) c.EXPECT().Run() }, }, { - name: "with COSIGN_PASSWORD env var and --verify=cosign", - cmdName: "pull", + name: "with --env-file flag replacement and existing env value", + cmdName: "run", fc: &config.Finch{}, - args: []string{"--verify=cosign", "test:tag"}, + args: []string{"--rm", "--env-file", envFilePath, "alpine:latest", "env"}, wantErr: nil, mockSvc: func( - _ *testing.T, + t *testing.T, lcc *mocks.LimaCmdCreator, _ *mocks.CommandCreator, ncsd *mocks.NerdctlCommandSystemDeps, logger *mocks.Logger, ctrl *gomock.Controller, - _ afero.Fs, + fs afero.Fs, ) { + envFileStr := "# a comment\n ARG2\n\n # a 2nd comment\nNOTSETARG\n " + require.NoError(t, afero.WriteFile(fs, envFilePath, []byte(envFileStr), 0o600)) + getVMStatusC := mocks.NewCommand(ctrl) lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) @@ -708,19 +881,44 @@ func TestNerdctlCommand_run(t *testing.T) { ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) - ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("test", true) - ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", "COSIGN_PASSWORD=test", nerdctlCmdName, - "pull", "--verify=cosign", "test:tag").Return(c) + ncsd.EXPECT().LookupEnv("ARG2").Return("val2", true) + ncsd.EXPECT().LookupEnv("NOTSETARG") + ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) + ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) + lcc.EXPECT(). + Create( + "shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "--rm", "-e", "ARG2=val2", "alpine:latest", "env", + ).Return(c) c.EXPECT().Run() }, }, { - name: "with COSIGN_PASSWORD env var without cosign arg", - cmdName: "pull", + name: "with --env-file flag, but the specified file does not exist", + cmdName: "run", + args: []string{"--rm", "--env-file", envFilePath, "alpine:latest", "env"}, + wantErr: &os.PathError{Op: "open", Path: envFilePath, Err: afero.ErrFileNotFound}, + mockSvc: func( + _ *testing.T, + lcc *mocks.LimaCmdCreator, + _ *mocks.CommandCreator, + _ *mocks.NerdctlCommandSystemDeps, + logger *mocks.Logger, + ctrl *gomock.Controller, + _ afero.Fs, + ) { + getVMStatusC := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) + getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + }, + }, + { + name: "with --add-host flag and special IP by space", + cmdName: "run", fc: &config.Finch{}, - args: []string{"test:tag"}, + args: []string{"--rm", "--add-host", "name:host-gateway", "alpine:latest"}, wantErr: nil, mockSvc: func( _ *testing.T, @@ -738,26 +936,25 @@ func TestNerdctlCommand_run(t *testing.T) { ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) - ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("test", true) + logger.EXPECT().Debugf(`Resolving special IP "host-gateway" to %q for host %q`, "192.168.5.2", "name") + ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", "COSIGN_PASSWORD=test", - nerdctlCmdName, "pull", "test:tag").Return(c) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "--rm", "--add-host", "name:192.168.5.2", "alpine:latest").Return(c) c.EXPECT().Run() }, }, { - name: "with ECR credential helper and environment set", - cmdName: "pull", - fc: &config.Finch{ - CredsHelpers: []string{"ecr-login"}, - }, - args: []string{"test:tag"}, + name: "with --add-host flag but without using special IP by space", + cmdName: "run", + fc: &config.Finch{}, + args: []string{"--rm", "--add-host", "name:0.0.0.0", "alpine:latest"}, wantErr: nil, mockSvc: func( _ *testing.T, lcc *mocks.LimaCmdCreator, - ecc *mocks.CommandCreator, + _ *mocks.CommandCreator, ncsd *mocks.NerdctlCommandSystemDeps, logger *mocks.Logger, ctrl *gomock.Controller, @@ -767,57 +964,57 @@ func TestNerdctlCommand_run(t *testing.T) { lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") - ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("TEST_ACCESS_KEY", true) - ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("TEST_SECRET_ACCESS_KEY", true) - ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("TEST_SESSION_TOKEN", true) + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) - - awsCmd := mocks.NewCommand(ctrl) - ecc.EXPECT().Create( - "aws", - "configure", - "export-credentials", - "--format", - "process", - ).Return(awsCmd) - awsCmd.EXPECT().CombinedOutput().Return([]byte(`{ - "AccessKeyID": "TEST_ACCESS_KEY_FROM_PROCESS", - "SecretAccessKey": "TEST_SECRET_ACCESS_KEY_FROM_PROCESS", - "SessionToken": "TEST_SESSION_TOKEN_FROM_PROCESS" -} -`), nil) - c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", - limaInstanceName, - "sudo", - "-E", - "AWS_ACCESS_KEY_ID=TEST_ACCESS_KEY_FROM_PROCESS", - "AWS_SECRET_ACCESS_KEY=TEST_SECRET_ACCESS_KEY_FROM_PROCESS", - "AWS_SESSION_TOKEN=TEST_SESSION_TOKEN_FROM_PROCESS", - "AWS_ACCESS_KEY_ID=TEST_ACCESS_KEY", - "AWS_SECRET_ACCESS_KEY=TEST_SECRET_ACCESS_KEY", - "AWS_SESSION_TOKEN=TEST_SESSION_TOKEN", - nerdctlCmdName, - "pull", - "test:tag", - ).Return(c) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "--rm", "--add-host", "name:0.0.0.0", "alpine:latest").Return(c) c.EXPECT().Run() }, }, { - name: "with ECR credential helper, no environment set", - cmdName: "pull", - fc: &config.Finch{ - CredsHelpers: []string{"ecr-login"}, + name: "with --add-host flag but without subsequent arg", + cmdName: "run", + fc: &config.Finch{}, + args: []string{"--rm", "--add-host", "alpine:latest"}, + wantErr: errors.New("run cmd error"), + mockSvc: func( + _ *testing.T, + lcc *mocks.LimaCmdCreator, + _ *mocks.CommandCreator, + ncsd *mocks.NerdctlCommandSystemDeps, + logger *mocks.Logger, + ctrl *gomock.Controller, + _ afero.Fs, + ) { + getVMStatusC := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) + getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) + ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) + ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) + c := mocks.NewCommand(ctrl) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "--rm", "--add-host", "alpine:latest").Return(c) + c.EXPECT().Run().Return(errors.New("run cmd error")) }, - args: []string{"test:tag"}, + }, + { + name: "with --add-host flag and special IP by equal", + cmdName: "run", + fc: &config.Finch{}, + args: []string{"--rm", "--add-host=name:host-gateway", "alpine:latest"}, wantErr: nil, mockSvc: func( _ *testing.T, lcc *mocks.LimaCmdCreator, - ecc *mocks.CommandCreator, + _ *mocks.CommandCreator, ncsd *mocks.NerdctlCommandSystemDeps, logger *mocks.Logger, ctrl *gomock.Controller, @@ -827,54 +1024,62 @@ func TestNerdctlCommand_run(t *testing.T) { lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") - ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("TEST_ACCESS_KEY", false) - ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("TEST_SECRET_ACCESS_KEY", false) - ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("TEST_SESSION_TOKEN", false) + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) + logger.EXPECT().Debugf(`Resolving special IP "host-gateway" to %q for host %q`, "192.168.5.2", "name") + ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) + ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) + c := mocks.NewCommand(ctrl) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "--rm", "--add-host=name:192.168.5.2", "alpine:latest").Return(c) + c.EXPECT().Run() + }, + }, + { + name: "with --add-host flag but without using special IP by equal", + cmdName: "run", + fc: &config.Finch{}, + args: []string{"--rm", "--add-host=name:0.0.0.0", "alpine:latest"}, + wantErr: nil, + mockSvc: func( + _ *testing.T, + lcc *mocks.LimaCmdCreator, + _ *mocks.CommandCreator, + ncsd *mocks.NerdctlCommandSystemDeps, + logger *mocks.Logger, + ctrl *gomock.Controller, + _ afero.Fs, + ) { + getVMStatusC := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) + getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) - - awsCmd := mocks.NewCommand(ctrl) - ecc.EXPECT().Create( - "aws", - "configure", - "export-credentials", - "--format", - "process", - ).Return(awsCmd) - awsCmd.EXPECT().CombinedOutput().Return([]byte(`{ - "AccessKeyID": "TEST_ACCESS_KEY_FROM_PROCESS", - "SecretAccessKey": "TEST_SECRET_ACCESS_KEY_FROM_PROCESS", - "SessionToken": "TEST_SESSION_TOKEN_FROM_PROCESS" -} -`), nil) c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", - limaInstanceName, - "sudo", - "-E", - "AWS_ACCESS_KEY_ID=TEST_ACCESS_KEY_FROM_PROCESS", - "AWS_SECRET_ACCESS_KEY=TEST_SECRET_ACCESS_KEY_FROM_PROCESS", - "AWS_SESSION_TOKEN=TEST_SESSION_TOKEN_FROM_PROCESS", - nerdctlCmdName, - "pull", - "test:tag", - ).Return(c) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "--rm", "--add-host=name:0.0.0.0", "alpine:latest").Return(c) c.EXPECT().Run() }, }, { - name: "with ECR credential helper, aws command fails but environment is used", - cmdName: "pull", - fc: &config.Finch{ - CredsHelpers: []string{"ecr-login"}, + name: "with multiple nested volumes", + cmdName: "run", + fc: &config.Finch{}, + args: []string{ + "--rm", "-v", "/tmp:/tmp1/tmp2:rro", "--volume", "/tmp:/tmp1:rprivate,rro", "-v=/tmp:/tmp1/tmp2/tmp3/tmp4:rro", + "--volume=/tmp:/tmp1/tmp3/tmp4:rshared", "-v", "volume", "alpine:latest", }, - args: []string{"test:tag"}, wantErr: nil, mockSvc: func( _ *testing.T, lcc *mocks.LimaCmdCreator, - ecc *mocks.CommandCreator, + _ *mocks.CommandCreator, ncsd *mocks.NerdctlCommandSystemDeps, logger *mocks.Logger, ctrl *gomock.Controller, @@ -884,50 +1089,31 @@ func TestNerdctlCommand_run(t *testing.T) { lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") - ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("TEST_ACCESS_KEY", true) - ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("TEST_SECRET_ACCESS_KEY", true) - ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("TEST_SESSION_TOKEN", true) + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) - - awsCmd := mocks.NewCommand(ctrl) - ecc.EXPECT().Create( - "aws", - "configure", - "export-credentials", - "--format", - "process", - ).Return(awsCmd) - awsCmd.EXPECT().CombinedOutput().Return(nil, fmt.Errorf("an error")) - logger.EXPECT().Debugln("failed to run `aws configure` command") - c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", - limaInstanceName, - "sudo", - "-E", - "AWS_ACCESS_KEY_ID=TEST_ACCESS_KEY", - "AWS_SECRET_ACCESS_KEY=TEST_SECRET_ACCESS_KEY", - "AWS_SESSION_TOKEN=TEST_SESSION_TOKEN", - nerdctlCmdName, - "pull", - "test:tag", - ).Return(c) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "--rm", "-v", "/tmp:/tmp1/tmp2:rro", "--volume", "/tmp:/tmp1:rprivate,rro", "-v", "/tmp:/tmp1/tmp2/tmp3/tmp4:rro", + "--volume", "/tmp:/tmp1/tmp3/tmp4:rshared", "-v", "volume", "alpine:latest").Return(c) c.EXPECT().Run() }, }, { - name: "with ECR credential helper, aws command fails but returns unexpected response", - cmdName: "pull", - fc: &config.Finch{ - CredsHelpers: []string{"ecr-login"}, + name: "with multiple nested volumes with full container run command", + cmdName: "container", + fc: &config.Finch{}, + args: []string{ + "run", "--rm", "-v", "/tmp:/tmp1/tmp2:rro", "--volume", "/tmp:/tmp1:rprivate,rro", + "-v=/tmp:/tmp1/tmp2/tmp3/tmp4:rro", "--volume=/tmp:/tmp1/tmp3/tmp4:rshared", "-v", "volume", "alpine:latest", }, - args: []string{"test:tag"}, wantErr: nil, mockSvc: func( _ *testing.T, lcc *mocks.LimaCmdCreator, - ecc *mocks.CommandCreator, + _ *mocks.CommandCreator, ncsd *mocks.NerdctlCommandSystemDeps, logger *mocks.Logger, ctrl *gomock.Controller, @@ -937,36 +1123,178 @@ func TestNerdctlCommand_run(t *testing.T) { lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") - ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("TEST_ACCESS_KEY", true) - ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("TEST_SECRET_ACCESS_KEY", true) - ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("TEST_SESSION_TOKEN", true) + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) + c := mocks.NewCommand(ctrl) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "--rm", "-v", "/tmp:/tmp1/tmp2:rro", "--volume", "/tmp:/tmp1:rprivate,rro", + "-v=/tmp:/tmp1/tmp2/tmp3/tmp4:rro", "--volume=/tmp:/tmp1/tmp3/tmp4:rshared", + "-v", "volume", "alpine:latest").Return(c) + c.EXPECT().Run() + }, + }, + { + name: "with combo short flag parsing", + cmdName: "run", + fc: &config.Finch{}, + args: []string{ + "-ie", "ARG1=val1", "-dp", "8080:8080", + "--name", "myContainer", "--rm", "alpine:latest", "env", + }, + wantErr: nil, + mockSvc: func( + _ *testing.T, + lcc *mocks.LimaCmdCreator, + _ *mocks.CommandCreator, + ncsd *mocks.NerdctlCommandSystemDeps, + logger *mocks.Logger, + ctrl *gomock.Controller, + _ afero.Fs, + ) { + getVMStatusC := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) + getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) + ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) + ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) + c := mocks.NewCommand(ctrl) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", + "-i", "-d", "-p", "8080:8080", "--name", "myContainer", "--rm", "-e", "ARG1=val1", + "alpine:latest", "env").Return(c) + c.EXPECT().Run() + }, + }, + } - awsCmd := mocks.NewCommand(ctrl) - ecc.EXPECT().Create( - "aws", - "configure", - "export-credentials", - "--format", - "process", - ).Return(awsCmd) - awsCmd.EXPECT().CombinedOutput().Return([]byte("unexpected response"), nil) - logger.EXPECT().Debugln("`aws configure export-credentials` output is unexpected, is command available? " + - "This may result in a broken ecr-credential helper experience.") + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + lcc := mocks.NewLimaCmdCreator(ctrl) + ecc := mocks.NewCommandCreator(ctrl) + ncsd := mocks.NewNerdctlCommandSystemDeps(ctrl) + logger := mocks.NewLogger(ctrl) + fs := afero.NewMemMapFs() + tc.mockSvc(t, lcc, ecc, ncsd, logger, ctrl, fs) + + assert.Equal(t, tc.wantErr, newNerdctlCommand(lcc, ecc, ncsd, logger, fs, tc.fc).run(tc.cmdName, tc.args)) + }) + } +} + +func TestNerdctlCommand_run_miscCommand(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + cmdName string + fc *config.Finch + args []string + wantErr error + mockSvc func( + t *testing.T, + lcc *mocks.LimaCmdCreator, + ecc *mocks.CommandCreator, + ncsd *mocks.NerdctlCommandSystemDeps, + logger *mocks.Logger, + ctrl *gomock.Controller, + fs afero.Fs, + ) + }{ + { + name: "happy path", + cmdName: "build", + fc: &config.Finch{}, + args: []string{"-t", "demo", "."}, + wantErr: nil, + mockSvc: func( + _ *testing.T, + lcc *mocks.LimaCmdCreator, + _ *mocks.CommandCreator, + ncsd *mocks.NerdctlCommandSystemDeps, + logger *mocks.Logger, + ctrl *gomock.Controller, + _ afero.Fs, + ) { + getVMStatusC := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) + getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) + ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) + ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) c := mocks.NewCommand(ctrl) - lcc.EXPECT().Create("shell", - limaInstanceName, - "sudo", - "-E", - "AWS_ACCESS_KEY_ID=TEST_ACCESS_KEY", - "AWS_SECRET_ACCESS_KEY=TEST_SECRET_ACCESS_KEY", - "AWS_SESSION_TOKEN=TEST_SESSION_TOKEN", - nerdctlCmdName, - "pull", - "test:tag", - ).Return(c) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "build", "-t", "demo", ".").Return(c) + c.EXPECT().Run() + }, + }, + { + name: "with COSIGN_PASSWORD env var and --sign=cosign", + cmdName: "push", + fc: &config.Finch{}, + args: []string{"--sign=cosign", "test:tag"}, + wantErr: nil, + mockSvc: func( + _ *testing.T, + lcc *mocks.LimaCmdCreator, + _ *mocks.CommandCreator, + ncsd *mocks.NerdctlCommandSystemDeps, + logger *mocks.Logger, + ctrl *gomock.Controller, + _ afero.Fs, + ) { + getVMStatusC := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) + getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) + ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("test", true) + ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) + c := mocks.NewCommand(ctrl) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", "COSIGN_PASSWORD=test", nerdctlCmdName, + "push", "--sign=cosign", "test:tag").Return(c) + c.EXPECT().Run() + }, + }, + { + name: "exec without flags", + cmdName: "exec", + fc: &config.Finch{}, + // exec test-ctr sh -c echo foo > /tmp/test.txt + args: []string{"test-ctr", "sh", "-c", "echo", "foo", ">", "/tmp/test.txt"}, + wantErr: nil, + mockSvc: func( + _ *testing.T, + lcc *mocks.LimaCmdCreator, + _ *mocks.CommandCreator, + ncsd *mocks.NerdctlCommandSystemDeps, + logger *mocks.Logger, + ctrl *gomock.Controller, + _ afero.Fs, + ) { + getVMStatusC := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) + getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) + ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) + ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) + ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false) + c := mocks.NewCommand(ctrl) + lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "exec", + "test-ctr", "sh", "-c", "echo", "foo", ">", "/tmp/test.txt").Return(c) c.EXPECT().Run() }, }, diff --git a/cmd/finch/nerdctl_windows_test.go b/cmd/finch/nerdctl_windows_test.go index acc3c8d67..6dfdd8a9c 100644 --- a/cmd/finch/nerdctl_windows_test.go +++ b/cmd/finch/nerdctl_windows_test.go @@ -173,10 +173,10 @@ func TestNerdctlCommand_run(t *testing.T) { }, }, { - name: "with environment flags parsing and env value doesn't exist", + name: "with implicit env flag parsing; values do not exist", cmdName: "run", fc: &config.Finch{}, - args: []string{"--rm", "-e", "ARG1=val1", "--env=ARG2", "-eARG3", "alpine:latest", "env"}, + args: []string{"-it", "-e", "ARG0=val0", "-e", "ARG1", "--env=ARG2", "-eARG3", "--rm", "alpine:latest", "env"}, wantErr: nil, mockSvc: func( _ *testing.T, @@ -196,6 +196,7 @@ func TestNerdctlCommand_run(t *testing.T) { ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false) ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false) c := mocks.NewCommand(ctrl) + ncsd.EXPECT().LookupEnv("ARG1") ncsd.EXPECT().LookupEnv("ARG2") ncsd.EXPECT().LookupEnv("ARG3") ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false) @@ -206,7 +207,7 @@ func TestNerdctlCommand_run(t *testing.T) { ncsd.EXPECT().FilePathToSlash(augmentedPath).Return(wslPath) lcc.EXPECT().Create("shell", "--workdir", wslPath, limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", - "--rm", "-e", "ARG1=val1", "alpine:latest", "env").Return(c) + "-it", "--rm", "-e", "ARG0=val0", "alpine:latest", "env").Return(c) c.EXPECT().Run() }, }, @@ -646,7 +647,7 @@ func TestNerdctlCommand_run(t *testing.T) { ncsd.EXPECT().FilePathToSlash(augmentedPath).Return(wslPath).Times(3) c := mocks.NewCommand(ctrl) lcc.EXPECT().Create("shell", "--workdir", wslPath, limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", - "--rm", "-v", "/mnt/c/workdir:/tmp1/tmp2:rro", "-v=/mnt/c/workdir:/tmp1/tmp2/tmp3/tmp4:rro", + "--rm", "-v", "/mnt/c/workdir:/tmp1/tmp2:rro", "-v", "/mnt/c/workdir:/tmp1/tmp2/tmp3/tmp4:rro", "-v", "volume", "alpine:latest").Return(c) c.EXPECT().Run() }, @@ -977,7 +978,7 @@ func TestNerdctlCommand_Run_withBindMounts(t *testing.T) { c := mocks.NewCommand(ctrl) // alias substitution, run => container run lcc.EXPECT().Create("shell", "--workdir", wslPath, limaInstanceName, - "sudo", "-E", nerdctlCmdName, "container", "run", + "sudo", "-E", nerdctlCmdName, "container", "run", "--mount", ContainsStr("source=/mnt/c/workdir"), "alpine:latest").Return(c) c.EXPECT().Run() }, @@ -1050,7 +1051,7 @@ func TestNerdctlCommand_Run_withBindMounts(t *testing.T) { // alias substitution, run => container run lcc.EXPECT().Create("shell", "--workdir", wslPath, limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run", "--mount", - "type=notbind,source=C:/workdir,target=/app", "alpine:latest").Return(c) + ContainsStr("type=notbind"), "alpine:latest").Return(c) c.EXPECT().Run() }, }, @@ -1264,7 +1265,7 @@ func TestNerdctlCommand_run_BuildCommand(t *testing.T) { ncsd.EXPECT().FilePathToSlash(buildContext).Return(wslBuildContextPath).Times(2) c := mocks.NewCommand(ctrl) lcc.EXPECT().Create("shell", "--workdir", wslPath, limaInstanceName, - "sudo", "-E", nerdctlCmdName, "image", "build", "-f", wslBuildContextPath, wslBuildContextPath).Return(c) + "sudo", "-E", nerdctlCmdName, "image", "build", "-f", ContainsStr(wslBuildContextPath), wslBuildContextPath).Return(c) c.EXPECT().Run() }, }, @@ -1299,7 +1300,7 @@ func TestNerdctlCommand_run_BuildCommand(t *testing.T) { c := mocks.NewCommand(ctrl) lcc.EXPECT().Create("shell", "--workdir", wslPath, limaInstanceName, "sudo", "-E", nerdctlCmdName, "image", "build", "--secret", - fmt.Sprintf("src=%s", wslSecretPath), wslBuildContextPath).Return(c) + ContainsStr(wslSecretPath), ContainsStr(wslBuildContextPath)).Return(c) c.EXPECT().Run() }, }, diff --git a/go.mod b/go.mod index 2c4027e11..99414c6d7 100644 --- a/go.mod +++ b/go.mod @@ -82,6 +82,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sergi/go-diff v1.3.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/wk8/go-ordered-map v1.0.0 go.opencensus.io v0.24.0 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect diff --git a/go.sum b/go.sum index cf22250ff..2d60f1c62 100644 --- a/go.sum +++ b/go.sum @@ -173,6 +173,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -189,6 +190,8 @@ github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2thqr8= +github.com/wk8/go-ordered-map v1.0.0/go.mod h1:9ZIbRunKbuvfPKyBP1SIKLcXNlv74YCOZ3t3VTS6gRk= github.com/xorcare/pointer v1.2.2 h1:zjD77b5DTehClND4MK+9dDE0DcpFIZisAJ/+yVJvKYA= github.com/xorcare/pointer v1.2.2/go.mod h1:azsKh7oVwYB7C1o8P284fG8MvtErX/F5/dqXiaj71ak= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=