Skip to content

Commit

Permalink
[#213] Add quiet flag, to the list command. (#221)
Browse files Browse the repository at this point in the history
* [#213] Add quiet flag, to the list command
---------

Signed-off-by: Daniel Milchev [email protected]
  • Loading branch information
daniel-milchev authored Dec 21, 2023
1 parent 92bc5e5 commit 7ad0d20
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 35 deletions.
63 changes: 61 additions & 2 deletions containerm/cli/cli_command_ctrs_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"fmt"
"os"
"strconv"
"strings"
"text/tabwriter"

"github.com/eclipse-kanto/container-management/containerm/client"
Expand All @@ -30,7 +31,9 @@ type listCmd struct {
}

type listConfig struct {
name string
name string
quiet bool
filter []string
}

func (cc *listCmd) init(cli *cli) {
Expand All @@ -43,7 +46,7 @@ func (cc *listCmd) init(cli *cli) {
RunE: func(cmd *cobra.Command, args []string) error {
return cc.run(args)
},
Example: "list\n list --name <container-name>\n list -n <container-name>",
Example: " list\n list --name <container-name>\n list --quiet\n list --filter status=created",
}
cc.setupFlags()
}
Expand All @@ -57,6 +60,23 @@ func (cc *listCmd) run(args []string) error {
if err != nil {
return err
}
if len(cc.config.filter) > 0 {
filtered, err := filterBy(cc.config.filter, ctrs)
if err != nil {
return err
}
ctrs = filtered
}
if cc.config.quiet {
for i, ctr := range ctrs {
if i != len(ctrs)-1 {
fmt.Printf("%s ", ctr.ID)
} else {
fmt.Println(ctr.ID)
}
}
return nil
}
if len(ctrs) == 0 {
fmt.Println("No found containers.")
} else {
Expand All @@ -69,6 +89,45 @@ 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 a specified filter. The containers can be filtered by their status, image and exitcode.")
}

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=") {
convertedExitCode, err = strconv.Atoi(strings.TrimPrefix(inp, "exitcode="))
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("no filter: %s", strings.Split(inp, "=")[0])
}
}
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
}

/*
Expand Down
83 changes: 82 additions & 1 deletion containerm/cli/cli_command_ctrs_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ package main

import (
"context"
"fmt"
"testing"

"github.com/eclipse-kanto/container-management/containerm/client"
Expand All @@ -24,7 +25,9 @@ import (

const (
// command flags
listCmdFlagName = "name"
listCmdFlagName = "name"
listCmdFlagQuiet = "quiet"
listCmdFlagFilter = "filter"

// test input constants
listContainerID = "test-ctr"
Expand Down Expand Up @@ -117,6 +120,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,
Expand Down Expand Up @@ -172,6 +211,48 @@ func (listTc *listCommandTest) mockExecListByNameNoCtrs(args []string) error {
return nil
}

func (listTc *listCommandTest) mockExecListQuiet(args []string) error {
// setup expected calls
ctrs := []*types.Container{
{
ID: fmt.Sprintf("%s-%d", listContainerID, 1),
Name: listFlagName,
State: &types.State{},
},
{
ID: fmt.Sprintf("%s-%d", listContainerID, 2),
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 filter: test")
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")
Expand Down
4 changes: 2 additions & 2 deletions containerm/cli/cli_command_ctrs_stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (cc *stopCmd) init(cli *cli) {
RunE: func(cmd *cobra.Command, args []string) error {
return cc.run(args)
},
Example: "stop <container-id>\n stop --name <container-name>\n stop -n <container-name>",
Example: " stop <container-id>\n stop --name <container-name>\n stop -n <container-name>",
}
cc.setupFlags()
}
Expand Down Expand Up @@ -75,7 +75,7 @@ func (cc *stopCmd) run(args []string) error {
func (cc *stopCmd) setupFlags() {
flagSet := cc.cmd.Flags()
// init timeout flag
flagSet.Int64VarP(&cc.config.timeout, "timeout", "t", math.MinInt64, "Sets the timeout period in seconds to gracefully stop the container. When timeout expires the container process would be forcibly killed.")
flagSet.Int64VarP(&cc.config.timeout, "time", "t", math.MinInt64, "Sets the timeout period in seconds to gracefully stop the container. When timeout expires the container process would be forcibly killed.")
// init name flag
flagSet.StringVarP(&cc.config.name, "name", "n", "", "Stop a container with a specific name.")
// init force flag
Expand Down
2 changes: 1 addition & 1 deletion containerm/cli/cli_command_ctrs_stop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (

const (
// command flags
stopCmdFlagTimeout = "timeout"
stopCmdFlagTimeout = "time"
stopCmdFlagName = "name"
stopCmdFlagForce = "force"
stopCmdFlagSignal = "signal"
Expand Down
44 changes: 28 additions & 16 deletions integration/framework/cli/cmd_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ func RunCmdTestCases(t *testing.T, cmdList []TestCaseCMD) {
if cmd.setupCmd != nil {
runMultipleCommands(t, *cmd.setupCmd)
}
result := icmd.RunCmd(cmd.icmd)
checkArguments(t, &cmd.icmd)
result := icmd.RunCommand(cmd.icmd.Command[0], cmd.icmd.Command[1:]...)
if cmd.goldenFile != "" {
assert.Assert(t, golden.String(result.Stdout(), cmd.goldenFile))
}
Expand All @@ -122,25 +123,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) {
Expand All @@ -152,7 +164,7 @@ 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...),
expected: icmd.Expected{
ExitCode: cmd.Expected.ExitCode,
Timeout: cmd.Expected.Timeout,
Expand All @@ -174,7 +186,7 @@ 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...))
}
return &cmds
}
11 changes: 7 additions & 4 deletions integration/testdata/list-help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ Usage:
kanto-cm list

Examples:
list
list
list --name <container-name>
list -n <container-name>
list --quiet
list --filter status=created

Flags:
-h, --help help for list
-n, --name string List all containers with a specific name.
--filter strings Lists only containers with a specified filter. The containers can be filtered by their status, image and exitcode.
-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
Expand Down
45 changes: 39 additions & 6 deletions integration/testdata/list-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,46 @@ onExit:
- binary: "kanto-cm"
args: ["stop", "--host", "$KANTO_HOST", "-s", "SIGKILL", "-n", "list_containers_with_state_running"]
- binary: "kanto-cm"
args: ["remove", "--host", "$KANTO_HOST", "-n", "list_containers_with_state_running", "-f"]
args: ["remove", "--host", "$KANTO_HOST", "$(kanto-cm list --quiet)"]
---
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", "--quiet"]
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_state_stopped", "-f"]
args: ["remove", "--host", "$KANTO_HOST", "$(kanto-cm list --quiet)"]
---
name: list_containers_with_filter
setupCmd:
- binary: kanto-cm
args: ["create", "--host", "$KANTO_HOST", "-n", "list_containers_with_filter_one", "docker.io/library/influxdb:1.8.4"]
- binary: kanto-cm
args: ["create", "--host", "$KANTO_HOST", "-n", "list_containers_with_filter_two", "docker.io/library/influxdb:1.8.4"]
command:
binary: kanto-cm
args: ["list", "--host", "$KANTO_HOST", "--filter", "status=created"]
expected:
exitCode: 0
customResult:
type: REGEX
args: ["ID |Name |Image |Status |Finished At |Exit Code |
------------------------------------- |------------------------------------- |------------------------------------------------------------ |---------- |------------------------------ |---------- |
[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12} |list_containers_with_filter_one |docker.io/library/influxdb:1.8.4 |Created | |0 |
[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12} |list_containers_with_filter_two |docker.io/library/influxdb:1.8.4 |Created | |0 |"]
onExit:
- binary: "kanto-cm"
args: ["remove", "--host", "$KANTO_HOST", "-n", "list_containers_with_state_created", "-f"]
args: ["remove", "--host", "$KANTO_HOST", "$(kanto-cm list --quiet)"]
---
name: list_existing_container
setupCmd:
Expand All @@ -69,9 +104,7 @@ customResult:
]
onExit:
- binary: "kanto-cm"
args: ["remove", "--host", "$KANTO_HOST", "-n", "list_containers_ctr0", "-f"]
- binary: "kanto-cm"
args: ["remove", "--host", "$KANTO_HOST", "-n", "list_containers_ctr1", "-f"]
args: ["remove", "--host", "$KANTO_HOST", "$(kanto-cm list --quiet)"]
---
name: list_not_existing_container
command:
Expand Down
Loading

0 comments on commit 7ad0d20

Please sign in to comment.