diff --git a/containerm/cli/cli_command_ctrs_list.go b/containerm/cli/cli_command_ctrs_list.go index 878f490..e6ef99b 100644 --- a/containerm/cli/cli_command_ctrs_list.go +++ b/containerm/cli/cli_command_ctrs_list.go @@ -17,6 +17,8 @@ import ( "fmt" "os" "strconv" + + "strings" "text/tabwriter" "github.com/eclipse-kanto/container-management/containerm/client" @@ -30,7 +32,9 @@ type listCmd struct { } type listConfig struct { - name string + name string + quiet bool + filter []string } func (cc *listCmd) init(cli *cli) { @@ -43,7 +47,7 @@ func (cc *listCmd) init(cli *cli) { RunE: func(cmd *cobra.Command, args []string) error { return cc.run(args) }, - Example: "list\n list --name \n list -n ", + Example: " list\n list --name \n list -n \n list -q\n list --filter ", } cc.setupFlags() } @@ -57,6 +61,20 @@ func (cc *listCmd) run(args []string) error { if err != nil { return err } + if len(cc.config.filter) > 0 && len(ctrs) > 0 { + filtered, err := filterBy(cc.config.filter, ctrs) + if err != nil { + return err + } + ctrs = filtered + } + if cc.config.quiet != false && len(ctrs) > 0 { + for _, ctr := range ctrs { + fmt.Printf("%s ", ctr.ID) + } + fmt.Println() + return nil + } if len(ctrs) == 0 { fmt.Println("No found containers.") } else { @@ -69,6 +87,46 @@ func (cc *listCmd) setupFlags() { flagSet := cc.cmd.Flags() // init name flags flagSet.StringVarP(&cc.config.name, "name", "n", "", "List all containers with a specific name.") + flagSet.BoolVarP(&cc.config.quiet, "quiet", "q", false, "List only container IDs.") + flagSet.StringSliceVar(&cc.config.filter, "filter", nil, "Lists only containers with the filter.") +} + +func filterBy(input []string, ctrs []*types.Container) ([]*types.Container, error) { + var ( + holderStatus string + holderImage string + convertedExitCode int = -1 + err error + ) + filteredCtrs := []*types.Container{} + for _, inp := range input { + if strings.HasPrefix(inp, "status=") { + holderStatus = strings.TrimPrefix(inp, "status=") + } else if strings.HasPrefix(inp, "image=") { + holderImage = strings.TrimPrefix(inp, "image=") + } else if strings.HasPrefix(inp, "exitcode=") { + exitcodeHolder := strings.TrimPrefix(inp, "exitcode=") + convertedExitCode, err = strconv.Atoi(exitcodeHolder) + if err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("No such filter.") + } + } + for _, ctr := range ctrs { + if holderStatus != "" && !strings.EqualFold(ctr.State.Status.String(), holderStatus) { + continue + } + if holderImage != "" && !strings.EqualFold(ctr.Image.Name, holderImage) { + continue + } + if int64(convertedExitCode) != -1 && ctr.State.ExitCode != int64(convertedExitCode) { + continue + } + filteredCtrs = append(filteredCtrs, ctr) + } + return filteredCtrs, nil } /* diff --git a/containerm/cli/cli_command_ctrs_list_test.go b/containerm/cli/cli_command_ctrs_list_test.go index 883162a..a2aae74 100644 --- a/containerm/cli/cli_command_ctrs_list_test.go +++ b/containerm/cli/cli_command_ctrs_list_test.go @@ -24,7 +24,9 @@ import ( const ( // command flags - listCmdFlagName = "name" + listCmdFlagName = "name" + listCmdFlagQuiet = "quiet" + listCmdFlagFilter = "filter" // test input constants listContainerID = "test-ctr" @@ -117,6 +119,42 @@ func (listTc *listCommandTest) generateRunExecutionConfigs() map[string]testRunE }, mockExecution: listTc.mockExecListByNameNoCtrs, }, + "test_list_quiet": { + flags: map[string]string{ + listCmdFlagQuiet: "true", + }, + mockExecution: listTc.mockExecListQuiet, + }, + "test_list_with_filter_status": { + flags: map[string]string{ + listCmdFlagFilter: "status=creating", + }, + mockExecution: listTc.mockExecListWithFilter, + }, + "test_list_with_filter_image": { + flags: map[string]string{ + listCmdFlagFilter: "image=test", + }, + mockExecution: listTc.mockExecListWithFilter, + }, + "test_list_with_filter_exit_code": { + flags: map[string]string{ + listCmdFlagFilter: "exitcode=0", + }, + mockExecution: listTc.mockExecListWithFilter, + }, + "test_list_with_multiple_filters": { + flags: map[string]string{ + listCmdFlagFilter: "image=test,exitcode=0", + }, + mockExecution: listTc.mockExecListWithFilter, + }, + "test_list_with_filter_error": { + flags: map[string]string{ + listCmdFlagFilter: "test=test", + }, + mockExecution: listTc.mockExecListWithFilterError, + }, "test_list_by_name_err": { flags: map[string]string{ listCmdFlagName: listFlagName, @@ -172,6 +210,41 @@ func (listTc *listCommandTest) mockExecListByNameNoCtrs(args []string) error { return nil } +func (listTc *listCommandTest) mockExecListQuiet(args []string) error { + // setup expected calls + ctrs := []*types.Container{{ + ID: listContainerID, + Name: listFlagName, + State: &types.State{}, + }} + listTc.mockClient.EXPECT().List(context.Background()).Times(1).Return(ctrs, nil) + // no error expected + return nil +} + +func (listTc *listCommandTest) mockExecListWithFilter(args []string) error { + // setup expected calls + ctrs := []*types.Container{{ + ID: listContainerID, + Name: listFlagName, + State: &types.State{}, + }} + listTc.mockClient.EXPECT().List(context.Background()).Times(1).Return(ctrs, nil) + // no error expected + return nil +} + +func (listTc *listCommandTest) mockExecListWithFilterError(args []string) error { + err := log.NewError("No such filter.") + ctrs := []*types.Container{{ + ID: listContainerID, + Name: listFlagName, + State: &types.State{}, + }} + listTc.mockClient.EXPECT().List(context.Background()).Times(1).Return(ctrs, nil) + return err +} + func (listTc *listCommandTest) mockExecListByNameErrors(args []string) error { // setup expected calls err := log.NewError("failed to get containers list") diff --git a/integration/framework/cli/cmd_base.go b/integration/framework/cli/cmd_base.go index 4e27b52..610a608 100644 --- a/integration/framework/cli/cmd_base.go +++ b/integration/framework/cli/cmd_base.go @@ -105,7 +105,10 @@ func RunCmdTestCases(t *testing.T, cmdList []TestCaseCMD) { if cmd.setupCmd != nil { runMultipleCommands(t, *cmd.setupCmd) } - result := icmd.RunCmd(cmd.icmd) + checkArguments(t, &cmd.icmd) + fmt.Printf("Running command: %s %s\n", cmd.icmd.Command[0], cmd.icmd.Command[1:]) + result := icmd.RunCommand(cmd.icmd.Command[0], cmd.icmd.Command[1:]...) + // result := icmd.RunCmd(cmd.icmd) if cmd.goldenFile != "" { assert.Assert(t, golden.String(result.Stdout(), cmd.goldenFile)) } @@ -122,25 +125,36 @@ func RunCmdTestCases(t *testing.T, cmdList []TestCaseCMD) { } } -func runMultipleCommands(t *testing.T, cmdArr []icmd.Cmd) { - for _, cmd := range cmdArr { - result := icmd.RunCmd(cmd) - result.Assert(t, icmd.Expected{ExitCode: 0}) - } -} - -func buildCmd(binary string, args ...string) *icmd.Cmd { - envArgs := []string{} - for _, arg := range args { +func checkArguments(t *testing.T, cmd *icmd.Cmd) { + execCmd := []string{} + for _, arg := range cmd.Command { + if strings.HasPrefix(arg, "$(") && strings.HasSuffix(arg, ")") { + arg = strings.TrimPrefix(arg, "$(") + arg = strings.TrimSuffix(arg, ")") + arguments := strings.Split(arg, " ") + cmd := icmd.Command(arguments[0], arguments[1:]...) + checkArguments(t, &cmd) + result := icmd.RunCmd(cmd) + assert.Equal(t, result.ExitCode, 0) + execCmd = append(execCmd, strings.Split(strings.TrimSuffix(strings.TrimSuffix(result.Stdout(), "\n"), " "), " ")...) + continue + } if strings.HasPrefix(arg, "$") { if val, ok := os.LookupEnv(strings.TrimPrefix(arg, "$")); ok { arg = val } } - envArgs = append(envArgs, arg) + execCmd = append(execCmd, arg) + } + *cmd = icmd.Cmd{Command: execCmd} +} + +func runMultipleCommands(t *testing.T, cmdArr []icmd.Cmd) { + for _, cmd := range cmdArr { + checkArguments(t, &cmd) + result := icmd.RunCommand(cmd.Command[0], cmd.Command[1:]...) + result.Assert(t, icmd.Expected{ExitCode: 0}) } - cmd := icmd.Command(binary, envArgs...) - return &cmd } func assertCustomResult(t *testing.T, result icmd.Result, name string, args ...string) { @@ -152,7 +166,8 @@ func assertCustomResult(t *testing.T, result icmd.Result, name string, args ...s func fromAPITestCommand(cmd TestCommand) TestCaseCMD { return TestCaseCMD{ name: cmd.Name, - icmd: *buildCmd(cmd.Command.Binary, cmd.Command.Args...), + icmd: icmd.Command(cmd.Command.Binary, cmd.Command.Args...), + // icmd: *checkArguments(cmd.Command.Binary, cmd.Command.Args...), expected: icmd.Expected{ ExitCode: cmd.Expected.ExitCode, Timeout: cmd.Expected.Timeout, @@ -174,7 +189,8 @@ func buildCmdArrFromCommand(cmd *[]Command) *[]icmd.Cmd { } cmds := make([]icmd.Cmd, 0) for _, cmd := range *cmd { - cmds = append(cmds, *buildCmd(cmd.Binary, cmd.Args...)) + cmds = append(cmds, icmd.Command(cmd.Binary, cmd.Args...)) + // cmds = append(cmds, *checkArguments(cmd.Binary, cmd.Args...)) } return &cmds } diff --git a/integration/testdata/list-help.golden b/integration/testdata/list-help.golden index 32f3de7..df62823 100644 --- a/integration/testdata/list-help.golden +++ b/integration/testdata/list-help.golden @@ -4,13 +4,17 @@ Usage: kanto-cm list Examples: -list + list list --name list -n + list -q + list --filter Flags: - -h, --help help for list - -n, --name string List all containers with a specific name. + --filter strings Lists only containers with the filter. + -h, --help help for list + -n, --name string List all containers with a specific name. + -q, --quiet List only container IDs. Global Flags: --debug Switch commands log level to DEBUG mode diff --git a/integration/testdata/list-test.yaml b/integration/testdata/list-test.yaml index fc3bf9f..d36c3fd 100644 --- a/integration/testdata/list-test.yaml +++ b/integration/testdata/list-test.yaml @@ -50,6 +50,26 @@ onExit: - binary: "kanto-cm" args: ["remove", "--host", "$KANTO_HOST", "-n", "list_containers_with_state_created", "-f"] --- +name: list_containers_with_quiet +setupCmd: + - binary: kanto-cm + args: ["create", "--host", "$KANTO_HOST", "-n", "list_containers_with_quiet_one", "docker.io/library/influxdb:1.8.4"] + - binary: kanto-cm + args: ["create", "--host", "$KANTO_HOST", "-n", "list_containers_with_quiet_two", "docker.io/library/influxdb:1.8.4"] +command: + binary: kanto-cm + args: ["list", "--host", "$KANTO_HOST", "-q"] +expected: + exitCode: 0 +customResult: + type: REGEX + args: ["[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"] +onExit: + - binary: "kanto-cm" + args: ["remove", "--host", "$KANTO_HOST", "-n", "list_containers_with_quiet_one", "-f"] + - binary: "kanto-cm" + args: ["remove", "--host", "$KANTO_HOST", "-n", "list_containers_with_quiet_two", "-f"] +--- name: list_existing_container setupCmd: - binary: kanto-cm diff --git a/integration/testdata/remove-test.yaml b/integration/testdata/remove-test.yaml index dfec002..3de6ac9 100644 --- a/integration/testdata/remove-test.yaml +++ b/integration/testdata/remove-test.yaml @@ -58,4 +58,16 @@ onExit: - binary: kanto-cm args: ["stop", "--host", "$KANTO_HOST", "-s", "SIGKILL", "-n", "remove_container_with_state_running"] - binary: "kanto-cm" - args: ["remove", "--host", "$KANTO_HOST", "-n", "remove_container_with_state_running", "-f"] \ No newline at end of file + args: ["remove", "--host", "$KANTO_HOST", "-n", "remove_container_with_state_running", "-f"] +--- +name: remove_multiple_containers +setupCmd: + - binary: kanto-cm + args: ["create", "--host", "$KANTO_HOST", "-n", "remove_container_one", "docker.io/library/influxdb:1.8.4"] + - binary: kanto-cm + args: ["create", "--host", "$KANTO_HOST", "-n", "remove_container_two", "docker.io/library/influxdb:1.8.4"] +command: + binary: kanto-cm + args: ["remove", "--host", "$KANTO_HOST", "$(kanto-cm list --quiet)"] +expected: + exitCode: 0 \ No newline at end of file