-
Notifications
You must be signed in to change notification settings - Fork 177
Added created date to app images #735
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ package e2e | |
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"path/filepath" | ||
"regexp" | ||
"strings" | ||
|
@@ -23,7 +24,11 @@ func insertBundles(t *testing.T, cmd icmd.Cmd) { | |
|
||
func assertImageListOutput(t *testing.T, cmd icmd.Cmd, expected string) { | ||
result := icmd.RunCmd(cmd).Assert(t, icmd.Success) | ||
match, _ := regexp.MatchString(expected, result.Stdout()) | ||
stdout := result.Stdout() | ||
match, _ := regexp.MatchString(expected, stdout) | ||
if !match { | ||
fmt.Println(stdout) | ||
} | ||
assert.Assert(t, match) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can add a third argument here, instead of checking |
||
} | ||
|
||
|
@@ -46,6 +51,7 @@ func verifyImageIDListOutput(t *testing.T, cmd icmd.Cmd, count int, distinct int | |
for scanner.Scan() { | ||
lines = append(lines, scanner.Text()) | ||
counter[scanner.Text()]++ | ||
fmt.Println(scanner.Text()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we really print here? |
||
} | ||
if err := scanner.Err(); err != nil { | ||
assert.Error(t, err, "Verification failed") | ||
|
@@ -60,10 +66,10 @@ func TestImageList(t *testing.T) { | |
|
||
insertBundles(t, cmd) | ||
|
||
expected := `REPOSITORY TAG APP IMAGE ID APP NAME | ||
a-simple-app latest [a-f0-9]{12} simple | ||
b-simple-app latest [a-f0-9]{12} simple | ||
my.registry:5000/c-myapp latest [a-f0-9]{12} push-pull | ||
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) | ||
}) | ||
|
@@ -73,18 +79,18 @@ func TestImageListQuiet(t *testing.T) { | |
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) { | ||
cmd := info.configuredCmd | ||
insertBundles(t, cmd) | ||
verifyImageIDListOutput(t, cmd, 3, 2) | ||
verifyImageIDListOutput(t, cmd, 3, 3) | ||
}) | ||
} | ||
|
||
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 | ||
a-simple-app latest <none> [a-f0-9]{12} simple | ||
b-simple-app latest <none> [a-f0-9]{12} simple | ||
my.registry:5000/c-myapp latest <none> [a-f0-9]{12} push-pull | ||
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) | ||
}) | ||
|
@@ -115,7 +121,7 @@ Deleted: b-simple-app:latest`, | |
Err: `b-simple-app:latest: reference not found`, | ||
}) | ||
|
||
expectedOutput := "REPOSITORY TAG APP IMAGE ID APP NAME\n" | ||
expectedOutput := "REPOSITORY TAG APP IMAGE ID APP NAME CREATED\n" | ||
expectImageListOutput(t, cmd, expectedOutput) | ||
}) | ||
} | ||
|
@@ -133,8 +139,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 | ||
a-simple-app latest [a-f0-9]{12} simple | ||
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) | ||
|
||
|
@@ -183,63 +189,63 @@ a-simple-app latest [a-f0-9]{12} simple | |
// 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 | ||
a-simple-app latest [a-f0-9]{12} simple | ||
b-simple-app latest [a-f0-9]{12} simple | ||
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 | ||
a-simple-app 0.1 [a-f0-9]{12} simple | ||
a-simple-app latest [a-f0-9]{12} simple | ||
b-simple-app latest [a-f0-9]{12} simple | ||
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 | ||
a-simple-app 0.1 [a-f0-9]{12} simple | ||
a-simple-app latest [a-f0-9]{12} simple | ||
b-simple-app latest [a-f0-9]{12} simple | ||
c-simple-app latest [a-f0-9]{12} simple | ||
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 | ||
a-simple-app 0.1 [a-f0-9]{12} simple | ||
a-simple-app latest [a-f0-9]{12} simple | ||
b-simple-app 0.2 [a-f0-9]{12} simple | ||
b-simple-app latest [a-f0-9]{12} simple | ||
c-simple-app latest [a-f0-9]{12} simple | ||
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 | ||
a-simple-app 0.1 [a-f0-9]{12} simple | ||
a-simple-app latest [a-f0-9]{12} simple | ||
b-simple-app 0.2 [a-f0-9]{12} simple | ||
b-simple-app latest [a-f0-9]{12} simple | ||
c-simple-app latest [a-f0-9]{12} simple | ||
push-pull latest [a-f0-9]{12} push-pull | ||
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 | ||
a-simple-app 0.1 [a-f0-9]{12} simple | ||
a-simple-app latest [a-f0-9]{12} simple | ||
b-simple-app 0.2 [a-f0-9]{12} push-pull | ||
b-simple-app latest [a-f0-9]{12} simple | ||
c-simple-app latest [a-f0-9]{12} simple | ||
push-pull latest [a-f0-9]{12} push-pull | ||
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 | ||
`) | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,14 +6,16 @@ import ( | |
"io" | ||
"strings" | ||
"text/tabwriter" | ||
"time" | ||
|
||
"github.com/docker/app/internal/packager" | ||
"github.com/docker/app/internal/relocated" | ||
|
||
"github.com/docker/app/internal/store" | ||
"github.com/docker/cli/cli/command" | ||
"github.com/docker/cli/cli/config" | ||
"github.com/docker/distribution/reference" | ||
"github.com/docker/docker/pkg/stringid" | ||
units "github.com/docker/go-units" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
|
@@ -177,6 +179,16 @@ func getImageListColumns(options imageListOption) []imageListColumn { | |
imageListColumn{"APP NAME", func(p pkg) string { | ||
return p.bundle.Name | ||
}}, | ||
imageListColumn{"CREATED", func(p pkg) string { | ||
payload, err := packager.CustomPayload(p.bundle.Bundle) | ||
if err != nil { | ||
return "" | ||
} | ||
if createdPayload, ok := payload.(packager.CustomPayloadCreated); ok { | ||
return units.HumanDuration(time.Now().UTC().Sub(createdPayload.CreatedTime())) + " ago" | ||
} | ||
return "" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if it should be empty There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally, would use a go template, which would allow setting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep right, we're fixing it in a follow-up, thanks @thaJeztah 😸 |
||
}}, | ||
) | ||
return columns | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package packager | ||
|
||
import ( | ||
"encoding/json" | ||
"time" | ||
|
||
"github.com/deislabs/cnab-go/bundle" | ||
"github.com/docker/app/internal" | ||
) | ||
|
||
const ( | ||
// DockerAppCustomVersion1_0_0 is the custom payload version 1.0.0 | ||
DockerAppCustomVersion1_0_0 = "1.0.0" | ||
|
||
// DockerAppCustomVersionCurrent the current payload version | ||
DockerAppCustomVersionCurrent = DockerAppCustomVersion1_0_0 | ||
) | ||
|
||
// DockerAppCustom contains extension custom data that docker app injects | ||
// in the bundle. | ||
type DockerAppCustom struct { | ||
Version string `json:"version,omitempty"` | ||
Payload json.RawMessage `json:"payload,omitempty"` | ||
} | ||
|
||
// CustomPayloadCreated is a custom payload with a created time | ||
type CustomPayloadCreated interface { | ||
CreatedTime() time.Time | ||
} | ||
|
||
type payloadV1_0 struct { | ||
Created time.Time `json:"created"` | ||
} | ||
|
||
func (p payloadV1_0) CreatedTime() time.Time { | ||
return p.Created | ||
} | ||
|
||
func newCustomPayload() (json.RawMessage, error) { | ||
p := payloadV1_0{Created: time.Now().UTC()} | ||
j, err := json.Marshal(&p) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return j, nil | ||
} | ||
|
||
// CustomPayload parses and returns the bundle's custom payload | ||
func CustomPayload(b *bundle.Bundle) (interface{}, error) { | ||
custom, err := parseCustomPayload(b) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
switch version := custom.Version; version { | ||
case DockerAppCustomVersion1_0_0: | ||
var payload payloadV1_0 | ||
if err := json.Unmarshal(custom.Payload, &payload); err != nil { | ||
return nil, err | ||
} | ||
return payload, nil | ||
default: | ||
return nil, nil | ||
} | ||
} | ||
|
||
func parseCustomPayload(b *bundle.Bundle) (DockerAppCustom, error) { | ||
customMap, ok := b.Custom[internal.CustomDockerAppName] | ||
if !ok { | ||
return DockerAppCustom{}, nil | ||
} | ||
|
||
customJSON, err := json.Marshal(customMap) | ||
if err != nil { | ||
return DockerAppCustom{}, err | ||
} | ||
|
||
var custom DockerAppCustom | ||
if err = json.Unmarshal(customJSON, &custom); err != nil { | ||
return DockerAppCustom{}, err | ||
} | ||
|
||
return custom, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the error could be asserted here.