mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #31497 from dnephin/engine-local-image-data
Add a LastTagTime for images
This commit is contained in:
commit
8f3c526e78
7 changed files with 61 additions and 0 deletions
|
@ -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"
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue