Set a LastUpdated time in image metadata when an image tag is updated.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Daniel Nephin 2017-03-02 15:47:02 -05:00 committed by Sebastiaan van Stijn
parent 33fd3817b0
commit 016eea004b
No known key found for this signature in database
GPG key ID: 76698F39D527CE8C
7 changed files with 61 additions and 0 deletions

View file

@ -949,6 +949,12 @@ definitions:
type: "string"
BaseLayer:
type: "string"
Metadata:
type: "object"
properties:
LastTagTime:
type: "string"
format: "dateTime"
ImageSummary:
type: "object"

View file

@ -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:

View file

@ -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,
@ -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)

View file

@ -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
}

View file

@ -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

View file

@ -6,6 +6,7 @@ import (
"runtime"
"strings"
"sync"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digestset"
@ -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
@ -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()

View file

@ -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) {