From 688e7e5debb48c7e162fc1f523afbf3b988d789a Mon Sep 17 00:00:00 2001 From: Djordje Lukic Date: Wed, 6 May 2020 09:37:52 +0200 Subject: [PATCH] Implement quiet flag for ps command --- Makefile | 6 +- README.md | 11 ++++ cli/cmd/ps.go | 72 +++++++++++++-------- cli/cmd/ps_test.go | 96 ++++++++++++++++++++++++++++ cli/cmd/testdata/ps-out-quiet.golden | 2 + cli/cmd/testdata/ps-out.golden | 3 + cli/main.go | 2 +- go.mod | 1 + tests/e2e/e2e.go | 11 +++- 9 files changed, 174 insertions(+), 30 deletions(-) create mode 100644 cli/cmd/ps_test.go create mode 100644 cli/cmd/testdata/ps-out-quiet.golden create mode 100644 cli/cmd/testdata/ps-out.golden diff --git a/Makefile b/Makefile index 86c50fcbd..a5c565533 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ test: ## Run unit tests @docker build . \ --target test -cache-clear: # Clear the builder cache +cache-clear: ## Clear the builder cache @docker builder prune --force --filter type=exec.cachemount --filter=unused-for=24h lint: ## run linter(s) @@ -62,8 +62,8 @@ lint: ## run linter(s) help: ## Show help @echo Please specify a build target. The choices are: - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' FORCE: -.PHONY: all protos cli cross test cache-clear lint help +.PHONY: all protos cli e2e-local cross test cache-clear lint help diff --git a/README.md b/README.md index 154cff171..ee9005e86 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,14 @@ $ make ``` If you make changes to the `.proto` files, make sure to `make protos` to generate go code. + + +## Tests + +To run unit tests: + +``` +make test +``` + +If you need to update a golden file simply do `go test ./... -test.update-golden`. diff --git a/cli/cmd/ps.go b/cli/cmd/ps.go index 168dfa702..ba108aa29 100644 --- a/cli/cmd/ps.go +++ b/cli/cmd/ps.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "fmt" "os" "text/tabwriter" @@ -11,29 +12,50 @@ import ( "github.com/docker/api/client" ) -// PsCommand lists containers -var PsCommand = cobra.Command{ - Use: "ps", - Short: "List containers", - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - - c, err := client.New(ctx) - if err != nil { - return errors.Wrap(err, "cannot connect to backend") - } - - containers, err := c.ContainerService().List(ctx) - if err != nil { - return errors.Wrap(err, "fetch containers") - } - - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintf(w, "NAME\tIMAGE\tSTATUS\tCOMMAND\n") - format := "%s\t%s\t%s\t%s\n" - for _, c := range containers { - fmt.Fprintf(w, format, c.ID, c.Image, c.Status, c.Command) - } - return w.Flush() - }, +type psOpts struct { + quiet bool +} + +// PsCommand lists containers +func PsCommand() *cobra.Command { + var opts psOpts + cmd := &cobra.Command{ + Use: "ps", + Short: "List containers", + RunE: func(cmd *cobra.Command, args []string) error { + return runPs(cmd.Context(), opts) + }, + } + + cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs") + + return cmd +} + +func runPs(ctx context.Context, opts psOpts) error { + c, err := client.New(ctx) + if err != nil { + return errors.Wrap(err, "cannot connect to backend") + } + + containers, err := c.ContainerService().List(ctx) + if err != nil { + return errors.Wrap(err, "fetch containers") + } + + if opts.quiet { + for _, c := range containers { + fmt.Println(c.ID) + } + return nil + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "NAME\tIMAGE\tSTATUS\tCOMMAND\n") + format := "%s\t%s\t%s\t%s\n" + for _, c := range containers { + fmt.Fprintf(w, format, c.ID, c.Image, c.Status, c.Command) + } + + return w.Flush() } diff --git a/cli/cmd/ps_test.go b/cli/cmd/ps_test.go new file mode 100644 index 000000000..e078d12cd --- /dev/null +++ b/cli/cmd/ps_test.go @@ -0,0 +1,96 @@ +package cmd + +import ( + "context" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "gotest.tools/v3/assert" + "gotest.tools/v3/golden" + + apicontext "github.com/docker/api/context" + "github.com/docker/api/context/store" + _ "github.com/docker/api/example" +) + +type PsSuite struct { + suite.Suite + ctx context.Context + writer *os.File + reader *os.File + originalStdout *os.File + storeRoot string +} + +func (sut *PsSuite) BeforeTest(suiteName, testName string) { + ctx := context.Background() + ctx = apicontext.WithCurrentContext(ctx, "example") + dir, err := ioutil.TempDir("", "store") + require.Nil(sut.T(), err) + s, err := store.New( + store.WithRoot(dir), + ) + require.Nil(sut.T(), err) + + err = s.Create("example", store.TypedContext{ + Type: "example", + }) + require.Nil(sut.T(), err) + + sut.storeRoot = dir + + ctx = store.WithContextStore(ctx, s) + sut.ctx = ctx + + sut.originalStdout = os.Stdout + r, w, err := os.Pipe() + require.Nil(sut.T(), err) + + os.Stdout = w + sut.writer = w + sut.reader = r +} + +func (sut *PsSuite) getStdOut() string { + err := sut.writer.Close() + require.Nil(sut.T(), err) + + out, _ := ioutil.ReadAll(sut.reader) + + return string(out) +} + +func (sut *PsSuite) AfterTest(suiteName, testName string) { + os.Stdout = sut.originalStdout + err := os.RemoveAll(sut.storeRoot) + require.Nil(sut.T(), err) +} + +func (sut *PsSuite) TestPs() { + opts := psOpts{ + quiet: false, + } + + err := runPs(sut.ctx, opts) + assert.NilError(sut.T(), err) + + golden.Assert(sut.T(), sut.getStdOut(), "ps-out.golden") +} + +func (sut *PsSuite) TestPsQuiet() { + opts := psOpts{ + quiet: true, + } + + err := runPs(sut.ctx, opts) + assert.NilError(sut.T(), err) + + golden.Assert(sut.T(), sut.getStdOut(), "ps-out-quiet.golden") +} + +func TestPs(t *testing.T) { + suite.Run(t, new(PsSuite)) +} diff --git a/cli/cmd/testdata/ps-out-quiet.golden b/cli/cmd/testdata/ps-out-quiet.golden new file mode 100644 index 000000000..ceeb39db2 --- /dev/null +++ b/cli/cmd/testdata/ps-out-quiet.golden @@ -0,0 +1,2 @@ +id +1234 diff --git a/cli/cmd/testdata/ps-out.golden b/cli/cmd/testdata/ps-out.golden new file mode 100644 index 000000000..1b6a30ca8 --- /dev/null +++ b/cli/cmd/testdata/ps-out.golden @@ -0,0 +1,3 @@ +NAME IMAGE STATUS COMMAND +id nginx +1234 alpine diff --git a/cli/main.go b/cli/main.go index 7f81f46f2..d700cf41e 100644 --- a/cli/main.go +++ b/cli/main.go @@ -96,7 +96,7 @@ func main() { root.AddCommand( cmd.ContextCommand(), - &cmd.PsCommand, + cmd.PsCommand(), cmd.ServeCommand(), run.Command(), cmd.ExecCommand(), diff --git a/go.mod b/go.mod index 76685e88e..2a9f9cadd 100644 --- a/go.mod +++ b/go.mod @@ -31,4 +31,5 @@ require ( golang.org/x/text v0.3.2 // indirect google.golang.org/grpc v1.29.1 google.golang.org/protobuf v1.21.0 + gotest.tools/v3 v3.0.2 ) diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index 890018718..762612cdc 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -7,8 +7,9 @@ import ( "github.com/robpike/filter" - f "github.com/docker/api/tests/framework" g "github.com/onsi/gomega" + + f "github.com/docker/api/tests/framework" ) func main() { @@ -69,6 +70,14 @@ func main() { g.Expect(lines[2]).To(g.ContainSubstring("1234 alpine")) }) + It("can run quiet ps command", func() { + output := f.NewDockerCommand("ps", "-q").ExecOrDie() + lines := lines(output) + g.Expect(len(lines)).To(g.Equal(2)) + g.Expect(lines[0]).To(g.Equal("id")) + g.Expect(lines[1]).To(g.Equal("1234")) + }) + It("can run 'run' command", func() { output := f.NewDockerCommand("run", "nginx", "-p", "80:80").ExecOrDie() g.Expect(output).To(g.ContainSubstring("Running container \"nginx\" with name"))