Skip to content

Commit

Permalink
Merge pull request moby#31497 from dnephin/engine-local-image-data
Browse files Browse the repository at this point in the history
Add a LastTagTime for images
  • Loading branch information
thaJeztah authored Jun 27, 2017
2 parents da28210 + 016eea0 commit 8f3c526
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 0 deletions.
6 changes: 6 additions & 0 deletions api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,12 @@ definitions:
type: "string"
BaseLayer:
type: "string"
Metadata:
type: "object"
properties:
LastTagTime:
type: "string"
format: "dateTime"

ImageSummary:
type: "object"
Expand Down
6 changes: 6 additions & 0 deletions api/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ type ImageInspect struct {
VirtualSize int64
GraphDriver GraphDriverData
RootFS RootFS
Metadata ImageMetadata
}

// ImageMetadata contains engine-local data about the image
type ImageMetadata struct {
LastTagTime time.Time `json:",omitempty"`
}

// Container contains response of Engine API:
Expand Down
8 changes: 8 additions & 0 deletions daemon/image_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ func (daemon *Daemon) LookupImage(name string) (*types.ImageInspect, error) {
comment = img.History[len(img.History)-1].Comment
}

lastUpdated, err := daemon.stores[platform].imageStore.GetLastUpdated(img.ID())
if err != nil {
return nil, err
}

imageInspect := &types.ImageInspect{
ID: img.ID().String(),
RepoTags: repoTags,
Expand All @@ -79,6 +84,9 @@ func (daemon *Daemon) LookupImage(name string) (*types.ImageInspect, error) {
Size: size,
VirtualSize: size, // TODO: field unused, deprecate
RootFS: rootFSToAPIType(img.RootFS),
Metadata: types.ImageMetadata{
LastTagTime: lastUpdated,
},
}

imageInspect.GraphDriver.Name = daemon.GraphDriverName(platform)
Expand Down
3 changes: 3 additions & 0 deletions daemon/image_tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ func (daemon *Daemon) TagImageWithReference(imageID image.ID, platform string, n
return err
}

if err := daemon.stores[platform].imageStore.SetLastUpdated(imageID); err != nil {
return err
}
daemon.LogImageEvent(imageID.String(), reference.FamiliarString(newTag), "tag")
return nil
}
1 change: 1 addition & 0 deletions docs/api/version-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ keywords: "API, Docker, rcli, REST, documentation"
* `POST /session` is a new endpoint that can be used for running interactive long-running protocols between client and
the daemon. This endpoint is experimental and only available if the daemon is started with experimental features
enabled.
* `GET /images/(name)/get` now includes an `ImageMetadata` field which contains image metadata that is local to the engine and not part of the image config.

## v1.30 API changes

Expand Down
19 changes: 19 additions & 0 deletions image/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"runtime"
"strings"
"sync"
"time"

"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digestset"
Expand All @@ -23,6 +24,8 @@ type Store interface {
Search(partialID string) (ID, error)
SetParent(id ID, parent ID) error
GetParent(id ID) (ID, error)
SetLastUpdated(id ID) error
GetLastUpdated(id ID) (time.Time, error)
Children(id ID) []ID
Map() map[ID]*Image
Heads() map[ID]*Image
Expand Down Expand Up @@ -259,6 +262,22 @@ func (is *store) GetParent(id ID) (ID, error) {
return ID(d), nil // todo: validate?
}

// SetLastUpdated time for the image ID to the current time
func (is *store) SetLastUpdated(id ID) error {
lastUpdated := []byte(time.Now().Format(time.RFC3339Nano))
return is.fs.SetMetadata(id.Digest(), "lastUpdated", lastUpdated)
}

// GetLastUpdated time for the image ID
func (is *store) GetLastUpdated(id ID) (time.Time, error) {
bytes, err := is.fs.GetMetadata(id.Digest(), "lastUpdated")
if err != nil || len(bytes) == 0 {
// No lastUpdated time
return time.Time{}, nil
}
return time.Parse(time.RFC3339Nano, string(bytes))
}

func (is *store) Children(id ID) []ID {
is.RLock()
defer is.RUnlock()
Expand Down
18 changes: 18 additions & 0 deletions image/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,24 @@ func defaultImageStore(t *testing.T) (Store, func()) {
return store, cleanup
}

func TestGetAndSetLastUpdated(t *testing.T) {
store, cleanup := defaultImageStore(t)
defer cleanup()

id, err := store.Create([]byte(`{"comment": "abc1", "rootfs": {"type": "layers"}}`))
assert.NoError(t, err)

updated, err := store.GetLastUpdated(id)
assert.NoError(t, err)
assert.Equal(t, updated.IsZero(), true)

assert.NoError(t, store.SetLastUpdated(id))

updated, err = store.GetLastUpdated(id)
assert.NoError(t, err)
assert.Equal(t, updated.IsZero(), false)
}

type mockLayerGetReleaser struct{}

func (ls *mockLayerGetReleaser) Get(layer.ChainID) (layer.Layer, error) {
Expand Down

0 comments on commit 8f3c526

Please sign in to comment.