Skip to content
This repository has been archived by the owner on Jun 13, 2021. It is now read-only.

Support format option in docker app ls #743

Merged
merged 6 commits into from
Nov 20, 2019
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
2 changes: 2 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

87 changes: 44 additions & 43 deletions e2e/images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,12 @@ func TestImageList(t *testing.T) {

insertBundles(t, cmd)

expected := `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
my.registry:5000/c-myapp latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
expected := `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
my.registry:5000/c-myapp latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago[ ]*
`

expectImageListOutput(t, cmd, expected)
})
}
Expand All @@ -87,10 +88,10 @@ func TestImageListDigests(t *testing.T) {
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
cmd := info.configuredCmd
insertBundles(t, cmd)
expected := `REPOSITORY TAG DIGEST APP IMAGE ID APP NAME CREATED
a-simple-app latest <none> [a-f0-9]{12} simple [La-z0-9 ]+ ago
b-simple-app latest <none> [a-f0-9]{12} simple [La-z0-9 ]+ ago
my.registry:5000/c-myapp latest <none> [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
expected := `REPOSITORY TAG DIGEST APP IMAGE ID APP NAME CREATED
a-simple-app latest <none> [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
b-simple-app latest <none> [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
my.registry:5000/c-myapp latest <none> [a-f0-9]{12} push-pull [La-z0-9 ]+ ago[ ]*
`
expectImageListDigestsOutput(t, cmd, expected)
})
Expand Down Expand Up @@ -121,7 +122,7 @@ Deleted: b-simple-app:latest`,
Err: `b-simple-app:latest: reference not found`,
})

expectedOutput := "REPOSITORY TAG APP IMAGE ID APP NAME CREATED\n"
expectedOutput := "REPOSITORY TAG APP IMAGE ID APP NAME CREATED \n"
expectImageListOutput(t, cmd, expectedOutput)
})
}
Expand All @@ -139,8 +140,8 @@ func TestImageTag(t *testing.T) {
cmd.Command = dockerCli.Command("app", "build", "--tag", "a-simple-app", filepath.Join("testdata", "simple"))
icmd.RunCmd(cmd).Assert(t, icmd.Success)

singleImageExpectation := `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
singleImageExpectation := `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
`
expectImageListOutput(t, cmd, singleImageExpectation)

Expand Down Expand Up @@ -189,63 +190,63 @@ a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
// tag image with only names
dockerAppImageTag("a-simple-app", "b-simple-app")
icmd.RunCmd(cmd).Assert(t, icmd.Success)
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
`)

// target tag
dockerAppImageTag("a-simple-app", "a-simple-app:0.1")
icmd.RunCmd(cmd).Assert(t, icmd.Success)
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
`)

// source tag
dockerAppImageTag("a-simple-app:0.1", "c-simple-app")
icmd.RunCmd(cmd).Assert(t, icmd.Success)
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
`)

// source and target tags
dockerAppImageTag("a-simple-app:0.1", "b-simple-app:0.2")
icmd.RunCmd(cmd).Assert(t, icmd.Success)
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
b-simple-app 0.2 [a-f0-9]{12} simple [La-z0-9 ]+ ago
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
b-simple-app 0.2 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
`)

// given a new application
cmd.Command = dockerCli.Command("app", "build", "--tag", "push-pull", filepath.Join("testdata", "push-pull"))
icmd.RunCmd(cmd).Assert(t, icmd.Success)
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
b-simple-app 0.2 [a-f0-9]{12} simple [La-z0-9 ]+ ago
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
push-pull latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
b-simple-app 0.2 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
push-pull latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago[ ]*
`)

// can be tagged to an existing tag
dockerAppImageTag("push-pull", "b-simple-app:0.2")
icmd.RunCmd(cmd).Assert(t, icmd.Success)
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
b-simple-app 0.2 [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
push-pull latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
b-simple-app 0.2 [a-f0-9]{12} push-pull [La-z0-9 ]+ ago[ ]*
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
push-pull latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago[ ]*
`)
})
}
126 changes: 126 additions & 0 deletions internal/commands/image/formatter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package image

import (
"time"

"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/go-units"
)

const (
defaultImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Name}}\t{{if .CreatedSince }}{{.CreatedSince}}{{else}}N/A{{end}}\t"
defaultImageTableFormatWithDigest = "table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.Name}}\t\t{{if .CreatedSince }}{{.CreatedSince}}{{else}}N/A{{end}}\t"

imageIDHeader = "APP IMAGE ID"
repositoryHeader = "REPOSITORY"
tagHeader = "TAG"
digestHeader = "DIGEST"
imageNameHeader = "APP NAME"
)

// NewImageFormat returns a format for rendering an ImageContext
func NewImageFormat(source string, quiet bool, digest bool) formatter.Format {
switch source {
case formatter.TableFormatKey:
switch {
case quiet:
return formatter.DefaultQuietFormat
case digest:
return defaultImageTableFormatWithDigest
default:
return defaultImageTableFormat
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have written it like:

if source == formatter.TableFormatKey {
        if quiet {
                return formatter.DefaultQuietFormat
        }
        if digest {
                return defaultImageTableFormatWithDigest
        }
        return defaultImageTableFormat
}

I don't see the usage of switch with only one case and switch on different variables.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this code was a (simplified) version of the original formatter from docker/cli. Wanted to keep implementations in sync

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I understand.


format := formatter.Format(source)
if format.IsTable() && digest && !format.Contains("{{.Digest}}") {
format += "\t{{.Digest}}"
}
return format
}

// Write writes the formatter images using the ImageContext
func Write(ctx formatter.Context, images []imageDesc) error {
render := func(format func(subContext formatter.SubContext) error) error {
return imageFormat(ctx, images, format)
}
return ctx.Write(newImageContext(), render)
}

func imageFormat(ctx formatter.Context, images []imageDesc, format func(subContext formatter.SubContext) error) error {
for _, image := range images {
img := &imageContext{
trunc: ctx.Trunc,
i: image}
if err := format(img); err != nil {
return err
}
}
return nil
}

type imageContext struct {
formatter.HeaderContext
trunc bool
i imageDesc
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not ?

Suggested change
i imageDesc
image imageDesc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same reason

}

func newImageContext() *imageContext {
imageCtx := imageContext{}
imageCtx.Header = formatter.SubHeaderContext{
"ID": imageIDHeader,
"Name": imageNameHeader,
"Repository": repositoryHeader,
"Tag": tagHeader,
"Digest": digestHeader,
"CreatedSince": formatter.CreatedSinceHeader,
}
return &imageCtx
}

func (c *imageContext) MarshalJSON() ([]byte, error) {
return formatter.MarshalJSON(c)
}

func (c *imageContext) ID() string {
if c.trunc {
return stringid.TruncateID(c.i.ID)
}
return c.i.ID
}

func (c *imageContext) Name() string {
if c.i.Name == "" {
return "<none>"
}
return c.i.Name
}

func (c *imageContext) Repository() string {
if c.i.Repository == "" {
return "<none>"
}
return c.i.Repository
}

func (c *imageContext) Tag() string {
if c.i.Tag == "" {
return "<none>"
}
return c.i.Tag
}

func (c *imageContext) Digest() string {
if c.i.Digest == "" {
return "<none>"
}
return c.i.Digest
}

func (c *imageContext) CreatedSince() string {
if c.i.Created.IsZero() {
return ""
}
return units.HumanDuration(time.Now().UTC().Sub(c.i.Created)) + " ago"
}
Loading