Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter and quiet flag for list CLI command. #221

Merged
merged 4 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.")
dimitar-dimitrow marked this conversation as resolved.
Show resolved Hide resolved
}

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
daniel-milchev marked this conversation as resolved.
Show resolved Hide resolved
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
Loading