Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit 7fc0612

Browse files
authored
Merge pull request #735 from zappy-shu/add-creation-to-bundles
Added created date to app images
2 parents 04214e3 + 9fb9b2d commit 7fc0612

File tree

8 files changed

+253
-66
lines changed

8 files changed

+253
-66
lines changed

e2e/images_test.go

Lines changed: 51 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package e2e
22

33
import (
44
"bufio"
5+
"fmt"
56
"path/filepath"
67
"regexp"
78
"strings"
@@ -23,7 +24,11 @@ func insertBundles(t *testing.T, cmd icmd.Cmd) {
2324

2425
func assertImageListOutput(t *testing.T, cmd icmd.Cmd, expected string) {
2526
result := icmd.RunCmd(cmd).Assert(t, icmd.Success)
26-
match, _ := regexp.MatchString(expected, result.Stdout())
27+
stdout := result.Stdout()
28+
match, _ := regexp.MatchString(expected, stdout)
29+
if !match {
30+
fmt.Println(stdout)
31+
}
2732
assert.Assert(t, match)
2833
}
2934

@@ -46,6 +51,7 @@ func verifyImageIDListOutput(t *testing.T, cmd icmd.Cmd, count int, distinct int
4651
for scanner.Scan() {
4752
lines = append(lines, scanner.Text())
4853
counter[scanner.Text()]++
54+
fmt.Println(scanner.Text())
4955
}
5056
if err := scanner.Err(); err != nil {
5157
assert.Error(t, err, "Verification failed")
@@ -60,10 +66,10 @@ func TestImageList(t *testing.T) {
6066

6167
insertBundles(t, cmd)
6268

63-
expected := `REPOSITORY TAG APP IMAGE ID APP NAME
64-
a-simple-app latest [a-f0-9]{12} simple
65-
b-simple-app latest [a-f0-9]{12} simple
66-
my.registry:5000/c-myapp latest [a-f0-9]{12} push-pull
69+
expected := `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
70+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
71+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
72+
my.registry:5000/c-myapp latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
6773
`
6874
expectImageListOutput(t, cmd, expected)
6975
})
@@ -73,18 +79,18 @@ func TestImageListQuiet(t *testing.T) {
7379
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
7480
cmd := info.configuredCmd
7581
insertBundles(t, cmd)
76-
verifyImageIDListOutput(t, cmd, 3, 2)
82+
verifyImageIDListOutput(t, cmd, 3, 3)
7783
})
7884
}
7985

8086
func TestImageListDigests(t *testing.T) {
8187
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
8288
cmd := info.configuredCmd
8389
insertBundles(t, cmd)
84-
expected := `REPOSITORY TAG DIGEST APP IMAGE ID APP NAME
85-
a-simple-app latest <none> [a-f0-9]{12} simple
86-
b-simple-app latest <none> [a-f0-9]{12} simple
87-
my.registry:5000/c-myapp latest <none> [a-f0-9]{12} push-pull
90+
expected := `REPOSITORY TAG DIGEST APP IMAGE ID APP NAME CREATED
91+
a-simple-app latest <none> [a-f0-9]{12} simple [La-z0-9 ]+ ago
92+
b-simple-app latest <none> [a-f0-9]{12} simple [La-z0-9 ]+ ago
93+
my.registry:5000/c-myapp latest <none> [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
8894
`
8995
expectImageListDigestsOutput(t, cmd, expected)
9096
})
@@ -115,7 +121,7 @@ Deleted: b-simple-app:latest`,
115121
Err: `b-simple-app:latest: reference not found`,
116122
})
117123

118-
expectedOutput := "REPOSITORY TAG APP IMAGE ID APP NAME\n"
124+
expectedOutput := "REPOSITORY TAG APP IMAGE ID APP NAME CREATED\n"
119125
expectImageListOutput(t, cmd, expectedOutput)
120126
})
121127
}
@@ -133,8 +139,8 @@ func TestImageTag(t *testing.T) {
133139
cmd.Command = dockerCli.Command("app", "build", "--tag", "a-simple-app", filepath.Join("testdata", "simple"))
134140
icmd.RunCmd(cmd).Assert(t, icmd.Success)
135141

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

@@ -183,63 +189,63 @@ a-simple-app latest [a-f0-9]{12} simple
183189
// tag image with only names
184190
dockerAppImageTag("a-simple-app", "b-simple-app")
185191
icmd.RunCmd(cmd).Assert(t, icmd.Success)
186-
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME
187-
a-simple-app latest [a-f0-9]{12} simple
188-
b-simple-app latest [a-f0-9]{12} simple
192+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
193+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
194+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
189195
`)
190196

191197
// target tag
192198
dockerAppImageTag("a-simple-app", "a-simple-app:0.1")
193199
icmd.RunCmd(cmd).Assert(t, icmd.Success)
194-
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME
195-
a-simple-app 0.1 [a-f0-9]{12} simple
196-
a-simple-app latest [a-f0-9]{12} simple
197-
b-simple-app latest [a-f0-9]{12} simple
200+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
201+
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
202+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
203+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
198204
`)
199205

200206
// source tag
201207
dockerAppImageTag("a-simple-app:0.1", "c-simple-app")
202208
icmd.RunCmd(cmd).Assert(t, icmd.Success)
203-
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME
204-
a-simple-app 0.1 [a-f0-9]{12} simple
205-
a-simple-app latest [a-f0-9]{12} simple
206-
b-simple-app latest [a-f0-9]{12} simple
207-
c-simple-app latest [a-f0-9]{12} simple
209+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
210+
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
211+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
212+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
213+
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
208214
`)
209215

210216
// source and target tags
211217
dockerAppImageTag("a-simple-app:0.1", "b-simple-app:0.2")
212218
icmd.RunCmd(cmd).Assert(t, icmd.Success)
213-
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME
214-
a-simple-app 0.1 [a-f0-9]{12} simple
215-
a-simple-app latest [a-f0-9]{12} simple
216-
b-simple-app 0.2 [a-f0-9]{12} simple
217-
b-simple-app latest [a-f0-9]{12} simple
218-
c-simple-app latest [a-f0-9]{12} simple
219+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
220+
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
221+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
222+
b-simple-app 0.2 [a-f0-9]{12} simple [La-z0-9 ]+ ago
223+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
224+
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
219225
`)
220226

221227
// given a new application
222228
cmd.Command = dockerCli.Command("app", "build", "--tag", "push-pull", filepath.Join("testdata", "push-pull"))
223229
icmd.RunCmd(cmd).Assert(t, icmd.Success)
224-
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME
225-
a-simple-app 0.1 [a-f0-9]{12} simple
226-
a-simple-app latest [a-f0-9]{12} simple
227-
b-simple-app 0.2 [a-f0-9]{12} simple
228-
b-simple-app latest [a-f0-9]{12} simple
229-
c-simple-app latest [a-f0-9]{12} simple
230-
push-pull latest [a-f0-9]{12} push-pull
230+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
231+
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
232+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
233+
b-simple-app 0.2 [a-f0-9]{12} simple [La-z0-9 ]+ ago
234+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
235+
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
236+
push-pull latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
231237
`)
232238

233239
// can be tagged to an existing tag
234240
dockerAppImageTag("push-pull", "b-simple-app:0.2")
235241
icmd.RunCmd(cmd).Assert(t, icmd.Success)
236-
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME
237-
a-simple-app 0.1 [a-f0-9]{12} simple
238-
a-simple-app latest [a-f0-9]{12} simple
239-
b-simple-app 0.2 [a-f0-9]{12} push-pull
240-
b-simple-app latest [a-f0-9]{12} simple
241-
c-simple-app latest [a-f0-9]{12} simple
242-
push-pull latest [a-f0-9]{12} push-pull
242+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
243+
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
244+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
245+
b-simple-app 0.2 [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
246+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
247+
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
248+
push-pull latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
243249
`)
244250
})
245251
}

internal/commands/image/list.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ import (
66
"io"
77
"strings"
88
"text/tabwriter"
9+
"time"
910

11+
"github.com/docker/app/internal/packager"
1012
"github.com/docker/app/internal/relocated"
11-
1213
"github.com/docker/app/internal/store"
1314
"github.com/docker/cli/cli/command"
1415
"github.com/docker/cli/cli/config"
1516
"github.com/docker/distribution/reference"
1617
"github.com/docker/docker/pkg/stringid"
18+
units "github.com/docker/go-units"
1719
"github.com/spf13/cobra"
1820
)
1921

@@ -177,6 +179,16 @@ func getImageListColumns(options imageListOption) []imageListColumn {
177179
imageListColumn{"APP NAME", func(p pkg) string {
178180
return p.bundle.Name
179181
}},
182+
imageListColumn{"CREATED", func(p pkg) string {
183+
payload, err := packager.CustomPayload(p.bundle.Bundle)
184+
if err != nil {
185+
return ""
186+
}
187+
if createdPayload, ok := payload.(packager.CustomPayloadCreated); ok {
188+
return units.HumanDuration(time.Now().UTC().Sub(createdPayload.CreatedTime())) + " ago"
189+
}
190+
return ""
191+
}},
180192
)
181193
return columns
182194
}

internal/commands/image/list_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,19 +83,19 @@ func TestListCmd(t *testing.T) {
8383
}{
8484
{
8585
name: "TestList",
86-
expectedOutput: `REPOSITORY TAG APP IMAGE ID APP NAME
87-
foo/bar <none> 3f825b2d0657 Digested App
88-
foo/bar 1.0 9aae408ee04f Foo App
89-
<none> <none> a855ac937f2e Quiet App
86+
expectedOutput: `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
87+
foo/bar <none> 3f825b2d0657 Digested App
88+
foo/bar 1.0 9aae408ee04f Foo App
89+
<none> <none> a855ac937f2e Quiet App
9090
`,
9191
options: imageListOption{},
9292
},
9393
{
9494
name: "TestListWithDigests",
95-
expectedOutput: `REPOSITORY TAG DIGEST APP IMAGE ID APP NAME
96-
foo/bar <none> sha256:b59492bb814012ca3d2ce0b6728242d96b4af41687cc82166a4b5d7f2d9fb865 3f825b2d0657 Digested App
97-
foo/bar 1.0 <none> 9aae408ee04f Foo App
98-
<none> <none> sha256:a855ac937f2ed375ba4396bbc49c4093e124da933acd2713fb9bc17d7562a087 a855ac937f2e Quiet App
95+
expectedOutput: `REPOSITORY TAG DIGEST APP IMAGE ID APP NAME CREATED
96+
foo/bar <none> sha256:b59492bb814012ca3d2ce0b6728242d96b4af41687cc82166a4b5d7f2d9fb865 3f825b2d0657 Digested App
97+
foo/bar 1.0 <none> 9aae408ee04f Foo App
98+
<none> <none> sha256:a855ac937f2ed375ba4396bbc49c4093e124da933acd2713fb9bc17d7562a087 a855ac937f2e Quiet App
9999
`,
100100
options: imageListOption{digests: true},
101101
},

internal/packager/cnab.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@ const (
1616
CNABVersion1_0_0 = "v1.0.0"
1717
)
1818

19-
// DockerAppCustom contains extension custom data that docker app injects
20-
// in the bundle.
21-
type DockerAppCustom struct {
22-
Version string `json:"version,omitempty"`
23-
Payload json.RawMessage `json:"payload,omitempty"`
24-
}
25-
2619
// DockerAppArgs represent the object passed to the invocation image
2720
// by Docker App.
2821
type DockerAppArgs struct {
@@ -170,11 +163,17 @@ func ToCNAB(app *types.App, invocationImageName string) (*bundle.Bundle, error)
170163
return nil, err
171164
}
172165

166+
payload, err := newCustomPayload()
167+
if err != nil {
168+
return nil, err
169+
}
170+
173171
bndl := &bundle.Bundle{
174172
SchemaVersion: CNABVersion1_0_0,
175173
Custom: map[string]interface{}{
176174
internal.CustomDockerAppName: DockerAppCustom{
177-
Version: internal.Version,
175+
Version: DockerAppCustomVersion1_0_0,
176+
Payload: payload,
178177
},
179178
},
180179
Credentials: map[string]bundle.Credential{

internal/packager/cnab_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package packager
33
import (
44
"encoding/json"
55
"fmt"
6+
"regexp"
67
"testing"
78

8-
"github.com/docker/app/internal"
99
"github.com/docker/app/types"
1010
"gotest.tools/assert"
1111
"gotest.tools/golden"
@@ -19,6 +19,9 @@ func TestToCNAB(t *testing.T) {
1919
actualJSON, err := json.MarshalIndent(actual, "", " ")
2020
assert.NilError(t, err)
2121
s := golden.Get(t, "bundle-json.golden")
22-
expected := fmt.Sprintf(string(s), internal.Version)
23-
assert.Equal(t, string(actualJSON), expected)
22+
expectedLiteral := regexp.QuoteMeta(string(s))
23+
expected := fmt.Sprintf(expectedLiteral, DockerAppCustomVersionCurrent, `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+Z`)
24+
matches, err := regexp.Match(expected, actualJSON)
25+
assert.NilError(t, err)
26+
assert.Assert(t, matches)
2427
}

internal/packager/custom.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package packager
2+
3+
import (
4+
"encoding/json"
5+
"time"
6+
7+
"github.com/deislabs/cnab-go/bundle"
8+
"github.com/docker/app/internal"
9+
)
10+
11+
const (
12+
// DockerAppCustomVersion1_0_0 is the custom payload version 1.0.0
13+
DockerAppCustomVersion1_0_0 = "1.0.0"
14+
15+
// DockerAppCustomVersionCurrent the current payload version
16+
DockerAppCustomVersionCurrent = DockerAppCustomVersion1_0_0
17+
)
18+
19+
// DockerAppCustom contains extension custom data that docker app injects
20+
// in the bundle.
21+
type DockerAppCustom struct {
22+
Version string `json:"version,omitempty"`
23+
Payload json.RawMessage `json:"payload,omitempty"`
24+
}
25+
26+
// CustomPayloadCreated is a custom payload with a created time
27+
type CustomPayloadCreated interface {
28+
CreatedTime() time.Time
29+
}
30+
31+
type payloadV1_0 struct {
32+
Created time.Time `json:"created"`
33+
}
34+
35+
func (p payloadV1_0) CreatedTime() time.Time {
36+
return p.Created
37+
}
38+
39+
func newCustomPayload() (json.RawMessage, error) {
40+
p := payloadV1_0{Created: time.Now().UTC()}
41+
j, err := json.Marshal(&p)
42+
if err != nil {
43+
return nil, err
44+
}
45+
return j, nil
46+
}
47+
48+
// CustomPayload parses and returns the bundle's custom payload
49+
func CustomPayload(b *bundle.Bundle) (interface{}, error) {
50+
custom, err := parseCustomPayload(b)
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
switch version := custom.Version; version {
56+
case DockerAppCustomVersion1_0_0:
57+
var payload payloadV1_0
58+
if err := json.Unmarshal(custom.Payload, &payload); err != nil {
59+
return nil, err
60+
}
61+
return payload, nil
62+
default:
63+
return nil, nil
64+
}
65+
}
66+
67+
func parseCustomPayload(b *bundle.Bundle) (DockerAppCustom, error) {
68+
customMap, ok := b.Custom[internal.CustomDockerAppName]
69+
if !ok {
70+
return DockerAppCustom{}, nil
71+
}
72+
73+
customJSON, err := json.Marshal(customMap)
74+
if err != nil {
75+
return DockerAppCustom{}, err
76+
}
77+
78+
var custom DockerAppCustom
79+
if err = json.Unmarshal(customJSON, &custom); err != nil {
80+
return DockerAppCustom{}, err
81+
}
82+
83+
return custom, nil
84+
}

0 commit comments

Comments
 (0)