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" type: "string"
BaseLayer: BaseLayer:
type: "string" type: "string"
Metadata:
type: "object"
properties:
LastTagTime:
type: "string"
format: "dateTime"
ImageSummary: ImageSummary:
type: "object" type: "object"

View File

@ -45,6 +45,12 @@ type ImageInspect struct {
VirtualSize int64 VirtualSize int64
GraphDriver GraphDriverData GraphDriver GraphDriverData
RootFS RootFS 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: // 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 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{ imageInspect := &types.ImageInspect{
ID: img.ID().String(), ID: img.ID().String(),
RepoTags: repoTags, RepoTags: repoTags,
@ -79,6 +84,9 @@ func (daemon *Daemon) LookupImage(name string) (*types.ImageInspect, error) {
Size: size, Size: size,
VirtualSize: size, // TODO: field unused, deprecate VirtualSize: size, // TODO: field unused, deprecate
RootFS: rootFSToAPIType(img.RootFS), RootFS: rootFSToAPIType(img.RootFS),
Metadata: types.ImageMetadata{
LastTagTime: lastUpdated,
},
} }
imageInspect.GraphDriver.Name = daemon.GraphDriverName(platform) imageInspect.GraphDriver.Name = daemon.GraphDriverName(platform)

View File

@ -32,6 +32,9 @@ func (daemon *Daemon) TagImageWithReference(imageID image.ID, platform string, n
return err return err
} }
if err := daemon.stores[platform].imageStore.SetLastUpdated(imageID); err != nil {
return err
}
daemon.LogImageEvent(imageID.String(), reference.FamiliarString(newTag), "tag") daemon.LogImageEvent(imageID.String(), reference.FamiliarString(newTag), "tag")
return nil 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 * `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 the daemon. This endpoint is experimental and only available if the daemon is started with experimental features
enabled. 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 ## v1.30 API changes

View File

@ -6,6 +6,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/distribution/digestset" "github.com/docker/distribution/digestset"
@ -23,6 +24,8 @@ type Store interface {
Search(partialID string) (ID, error) Search(partialID string) (ID, error)
SetParent(id ID, parent ID) error SetParent(id ID, parent ID) error
GetParent(id ID) (ID, error) GetParent(id ID) (ID, error)
SetLastUpdated(id ID) error
GetLastUpdated(id ID) (time.Time, error)
Children(id ID) []ID Children(id ID) []ID
Map() map[ID]*Image Map() map[ID]*Image
Heads() 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? 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 { func (is *store) Children(id ID) []ID {
is.RLock() is.RLock()
defer is.RUnlock() defer is.RUnlock()

View File

@ -149,6 +149,24 @@ func defaultImageStore(t *testing.T) (Store, func()) {
return store, cleanup 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{} type mockLayerGetReleaser struct{}
func (ls *mockLayerGetReleaser) Get(layer.ChainID) (layer.Layer, error) { func (ls *mockLayerGetReleaser) Get(layer.ChainID) (layer.Layer, error) {