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

Added created date to app images #735

Merged
merged 1 commit into from
Nov 12, 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
96 changes: 51 additions & 45 deletions e2e/images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package e2e

import (
"bufio"
"fmt"
"path/filepath"
"regexp"
"strings"
Expand All @@ -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)
Copy link
Contributor

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.

if !match {
fmt.Println(stdout)
}
assert.Assert(t, match)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you can add a third argument here, instead of checking match before assertion.
assert.Assert(t, match, stdout)

}

Expand All @@ -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())
Copy link
Contributor

Choose a reason for hiding this comment

The 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")
Expand All @@ -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)
})
Expand All @@ -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)
})
Expand Down Expand Up @@ -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)
})
}
Expand All @@ -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)

Expand Down Expand Up @@ -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
`)
})
}
14 changes: 13 additions & 1 deletion internal/commands/image/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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 ""
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it should be empty "" or "N/A" ? @thaJeztah WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Ideally, would use a go template, which would allow setting N/A in the template (separating presentation from the value); see docker/cli#2107

Copy link
Contributor

Choose a reason for hiding this comment

The 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
}
Expand Down
16 changes: 8 additions & 8 deletions internal/commands/image/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,19 +83,19 @@ func TestListCmd(t *testing.T) {
}{
{
name: "TestList",
expectedOutput: `REPOSITORY TAG APP IMAGE ID APP NAME
foo/bar <none> 3f825b2d0657 Digested App
foo/bar 1.0 9aae408ee04f Foo App
<none> <none> a855ac937f2e Quiet App
expectedOutput: `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
foo/bar <none> 3f825b2d0657 Digested App
foo/bar 1.0 9aae408ee04f Foo App
<none> <none> a855ac937f2e Quiet App
`,
options: imageListOption{},
},
{
name: "TestListWithDigests",
expectedOutput: `REPOSITORY TAG DIGEST APP IMAGE ID APP NAME
foo/bar <none> sha256:b59492bb814012ca3d2ce0b6728242d96b4af41687cc82166a4b5d7f2d9fb865 3f825b2d0657 Digested App
foo/bar 1.0 <none> 9aae408ee04f Foo App
<none> <none> sha256:a855ac937f2ed375ba4396bbc49c4093e124da933acd2713fb9bc17d7562a087 a855ac937f2e Quiet App
expectedOutput: `REPOSITORY TAG DIGEST APP IMAGE ID APP NAME CREATED
foo/bar <none> sha256:b59492bb814012ca3d2ce0b6728242d96b4af41687cc82166a4b5d7f2d9fb865 3f825b2d0657 Digested App
foo/bar 1.0 <none> 9aae408ee04f Foo App
<none> <none> sha256:a855ac937f2ed375ba4396bbc49c4093e124da933acd2713fb9bc17d7562a087 a855ac937f2e Quiet App
`,
options: imageListOption{digests: true},
},
Expand Down
15 changes: 7 additions & 8 deletions internal/packager/cnab.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@ const (
CNABVersion1_0_0 = "v1.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"`
}

// DockerAppArgs represent the object passed to the invocation image
// by Docker App.
type DockerAppArgs struct {
Expand Down Expand Up @@ -170,11 +163,17 @@ func ToCNAB(app *types.App, invocationImageName string) (*bundle.Bundle, error)
return nil, err
}

payload, err := newCustomPayload()
if err != nil {
return nil, err
}

bndl := &bundle.Bundle{
SchemaVersion: CNABVersion1_0_0,
Custom: map[string]interface{}{
internal.CustomDockerAppName: DockerAppCustom{
Version: internal.Version,
Version: DockerAppCustomVersion1_0_0,
Payload: payload,
},
},
Credentials: map[string]bundle.Credential{
Expand Down
9 changes: 6 additions & 3 deletions internal/packager/cnab_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package packager
import (
"encoding/json"
"fmt"
"regexp"
"testing"

"github.com/docker/app/internal"
"github.com/docker/app/types"
"gotest.tools/assert"
"gotest.tools/golden"
Expand All @@ -19,6 +19,9 @@ func TestToCNAB(t *testing.T) {
actualJSON, err := json.MarshalIndent(actual, "", " ")
assert.NilError(t, err)
s := golden.Get(t, "bundle-json.golden")
expected := fmt.Sprintf(string(s), internal.Version)
assert.Equal(t, string(actualJSON), expected)
expectedLiteral := regexp.QuoteMeta(string(s))
expected := fmt.Sprintf(expectedLiteral, DockerAppCustomVersionCurrent, `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+Z`)
matches, err := regexp.Match(expected, actualJSON)
assert.NilError(t, err)
assert.Assert(t, matches)
}
84 changes: 84 additions & 0 deletions internal/packager/custom.go
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
}
Loading