mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	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…
	
	Add table
		Add a link
		
	
		Reference in a new issue