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:
parent
33fd3817b0
commit
016eea004b
7 changed files with 61 additions and 0 deletions
|
@ -949,6 +949,12 @@ definitions:
|
|||
type: "string"
|
||||
BaseLayer:
|
||||
type: "string"
|
||||
Metadata:
|
||||
type: "object"
|
||||
properties:
|
||||
LastTagTime:
|
||||
type: "string"
|
||||
format: "dateTime"
|
||||
|
||||
ImageSummary:
|
||||
type: "object"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue