Bläddra i källkod

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>
Daniel Nephin 8 år sedan
förälder
incheckning
016eea004b
7 ändrade filer med 61 tillägg och 0 borttagningar
  1. 6 0
      api/swagger.yaml
  2. 6 0
      api/types/types.go
  3. 8 0
      daemon/image_inspect.go
  4. 3 0
      daemon/image_tag.go
  5. 1 0
      docs/api/version-history.md
  6. 19 0
      image/store.go
  7. 18 0
      image/store_test.go

+ 6 - 0
api/swagger.yaml

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

+ 6 - 0
api/types/types.go

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

+ 8 - 0
daemon/image_inspect.go

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

+ 3 - 0
daemon/image_tag.go

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

+ 1 - 0
docs/api/version-history.md

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

+ 19 - 0
image/store.go

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

+ 18 - 0
image/store_test.go

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