2015-11-18 17:18:07 -05:00
|
|
|
package image
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2017-05-19 13:38:47 -04:00
|
|
|
"strings"
|
2015-11-18 17:18:07 -05:00
|
|
|
"sync"
|
2017-03-02 15:47:02 -05:00
|
|
|
"time"
|
2015-11-18 17:18:07 -05:00
|
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
2017-01-06 20:23:18 -05:00
|
|
|
"github.com/docker/distribution/digestset"
|
2015-11-18 17:18:07 -05:00
|
|
|
"github.com/docker/docker/layer"
|
2017-05-19 13:38:47 -04:00
|
|
|
"github.com/docker/docker/pkg/system"
|
2017-01-06 20:23:18 -05:00
|
|
|
"github.com/opencontainers/go-digest"
|
2017-05-25 17:03:29 -04:00
|
|
|
"github.com/pkg/errors"
|
2015-11-18 17:18:07 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// Store is an interface for creating and accessing images
|
|
|
|
type Store interface {
|
|
|
|
Create(config []byte) (ID, error)
|
|
|
|
Get(id ID) (*Image, error)
|
|
|
|
Delete(id ID) ([]layer.Metadata, error)
|
|
|
|
Search(partialID string) (ID, error)
|
|
|
|
SetParent(id ID, parent ID) error
|
|
|
|
GetParent(id ID) (ID, error)
|
2017-03-02 15:47:02 -05:00
|
|
|
SetLastUpdated(id ID) error
|
|
|
|
GetLastUpdated(id ID) (time.Time, error)
|
2015-11-18 17:18:07 -05:00
|
|
|
Children(id ID) []ID
|
|
|
|
Map() map[ID]*Image
|
|
|
|
Heads() map[ID]*Image
|
|
|
|
}
|
|
|
|
|
|
|
|
// LayerGetReleaser is a minimal interface for getting and releasing images.
|
|
|
|
type LayerGetReleaser interface {
|
|
|
|
Get(layer.ChainID) (layer.Layer, error)
|
|
|
|
Release(layer.Layer) ([]layer.Metadata, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type imageMeta struct {
|
|
|
|
layer layer.Layer
|
|
|
|
children map[ID]struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
type store struct {
|
2017-06-20 21:31:29 -04:00
|
|
|
sync.RWMutex
|
2015-11-18 17:18:07 -05:00
|
|
|
ls LayerGetReleaser
|
|
|
|
images map[ID]*imageMeta
|
|
|
|
fs StoreBackend
|
2017-01-06 20:23:18 -05:00
|
|
|
digestSet *digestset.Set
|
2017-05-16 17:35:28 -04:00
|
|
|
platform string
|
2015-11-18 17:18:07 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewImageStore returns new store object for given layer store
|
2017-05-16 17:35:28 -04:00
|
|
|
func NewImageStore(fs StoreBackend, platform string, ls LayerGetReleaser) (Store, error) {
|
2015-11-18 17:18:07 -05:00
|
|
|
is := &store{
|
|
|
|
ls: ls,
|
|
|
|
images: make(map[ID]*imageMeta),
|
|
|
|
fs: fs,
|
2017-01-06 20:23:18 -05:00
|
|
|
digestSet: digestset.NewSet(),
|
2017-05-16 17:35:28 -04:00
|
|
|
platform: platform,
|
2015-11-18 17:18:07 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// load all current images and retain layers
|
|
|
|
if err := is.restore(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return is, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (is *store) restore() error {
|
2016-09-15 19:37:32 -04:00
|
|
|
err := is.fs.Walk(func(dgst digest.Digest) error {
|
|
|
|
img, err := is.Get(IDFromDigest(dgst))
|
2015-11-18 17:18:07 -05:00
|
|
|
if err != nil {
|
2016-09-15 19:37:32 -04:00
|
|
|
logrus.Errorf("invalid image %v, %v", dgst, err)
|
2015-11-18 17:18:07 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var l layer.Layer
|
|
|
|
if chainID := img.RootFS.ChainID(); chainID != "" {
|
|
|
|
l, err = is.ls.Get(chainID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2016-09-15 19:37:32 -04:00
|
|
|
if err := is.digestSet.Add(dgst); err != nil {
|
2015-11-18 17:18:07 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
imageMeta := &imageMeta{
|
|
|
|
layer: l,
|
|
|
|
children: make(map[ID]struct{}),
|
|
|
|
}
|
|
|
|
|
2016-09-15 19:37:32 -04:00
|
|
|
is.images[IDFromDigest(dgst)] = imageMeta
|
2015-11-18 17:18:07 -05:00
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Second pass to fill in children maps
|
|
|
|
for id := range is.images {
|
|
|
|
if parent, err := is.GetParent(id); err == nil {
|
|
|
|
if parentMeta := is.images[parent]; parentMeta != nil {
|
|
|
|
parentMeta.children[id] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (is *store) Create(config []byte) (ID, error) {
|
|
|
|
var img Image
|
|
|
|
err := json.Unmarshal(config, &img)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2017-06-26 12:11:54 -04:00
|
|
|
// TODO @jhowardmsft - LCOW Support. This will need revisiting.
|
2017-05-19 13:38:47 -04:00
|
|
|
// Integrity check - ensure we are creating something for the correct platform
|
2017-06-26 12:11:54 -04:00
|
|
|
if system.LCOWSupported() {
|
2017-05-19 13:38:47 -04:00
|
|
|
if strings.ToLower(img.Platform()) != strings.ToLower(is.platform) {
|
|
|
|
return "", fmt.Errorf("cannot create entry for platform %q in image store for platform %q", img.Platform(), is.platform)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-18 17:18:07 -05:00
|
|
|
// Must reject any config that references diffIDs from the history
|
|
|
|
// which aren't among the rootfs layers.
|
|
|
|
rootFSLayers := make(map[layer.DiffID]struct{})
|
|
|
|
for _, diffID := range img.RootFS.DiffIDs {
|
|
|
|
rootFSLayers[diffID] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
layerCounter := 0
|
|
|
|
for _, h := range img.History {
|
|
|
|
if !h.EmptyLayer {
|
|
|
|
layerCounter++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if layerCounter > len(img.RootFS.DiffIDs) {
|
|
|
|
return "", errors.New("too many non-empty layers in History section")
|
|
|
|
}
|
|
|
|
|
|
|
|
dgst, err := is.fs.Set(config)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2016-09-15 19:37:32 -04:00
|
|
|
imageID := IDFromDigest(dgst)
|
2015-11-18 17:18:07 -05:00
|
|
|
|
|
|
|
is.Lock()
|
|
|
|
defer is.Unlock()
|
|
|
|
|
|
|
|
if _, exists := is.images[imageID]; exists {
|
|
|
|
return imageID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
layerID := img.RootFS.ChainID()
|
|
|
|
|
|
|
|
var l layer.Layer
|
|
|
|
if layerID != "" {
|
|
|
|
l, err = is.ls.Get(layerID)
|
|
|
|
if err != nil {
|
2017-05-25 17:03:29 -04:00
|
|
|
return "", errors.Wrapf(err, "failed to get layer %s", layerID)
|
2015-11-18 17:18:07 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
imageMeta := &imageMeta{
|
|
|
|
layer: l,
|
|
|
|
children: make(map[ID]struct{}),
|
|
|
|
}
|
|
|
|
|
|
|
|
is.images[imageID] = imageMeta
|
2016-09-15 19:37:32 -04:00
|
|
|
if err := is.digestSet.Add(imageID.Digest()); err != nil {
|
2015-11-18 17:18:07 -05:00
|
|
|
delete(is.images, imageID)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return imageID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (is *store) Search(term string) (ID, error) {
|
|
|
|
dgst, err := is.digestSet.Lookup(term)
|
|
|
|
if err != nil {
|
2017-01-06 20:23:18 -05:00
|
|
|
if err == digestset.ErrDigestNotFound {
|
2016-03-04 05:05:34 -05:00
|
|
|
err = fmt.Errorf("No such image: %s", term)
|
|
|
|
}
|
2015-11-18 17:18:07 -05:00
|
|
|
return "", err
|
|
|
|
}
|
2016-09-15 19:37:32 -04:00
|
|
|
return IDFromDigest(dgst), nil
|
2015-11-18 17:18:07 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (is *store) Get(id ID) (*Image, error) {
|
|
|
|
// todo: Check if image is in images
|
|
|
|
// todo: Detect manual insertions and start using them
|
2016-09-15 19:37:32 -04:00
|
|
|
config, err := is.fs.Get(id.Digest())
|
2015-11-18 17:18:07 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
img, err := NewFromJSON(config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
img.computedID = id
|
|
|
|
|
|
|
|
img.Parent, err = is.GetParent(id)
|
|
|
|
if err != nil {
|
|
|
|
img.Parent = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return img, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (is *store) Delete(id ID) ([]layer.Metadata, error) {
|
|
|
|
is.Lock()
|
|
|
|
defer is.Unlock()
|
|
|
|
|
|
|
|
imageMeta := is.images[id]
|
|
|
|
if imageMeta == nil {
|
|
|
|
return nil, fmt.Errorf("unrecognized image ID %s", id.String())
|
|
|
|
}
|
|
|
|
for id := range imageMeta.children {
|
2016-09-15 19:37:32 -04:00
|
|
|
is.fs.DeleteMetadata(id.Digest(), "parent")
|
2015-11-18 17:18:07 -05:00
|
|
|
}
|
|
|
|
if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil {
|
|
|
|
delete(is.images[parent].children, id)
|
|
|
|
}
|
|
|
|
|
2016-09-15 19:37:32 -04:00
|
|
|
if err := is.digestSet.Remove(id.Digest()); err != nil {
|
2015-12-04 16:15:54 -05:00
|
|
|
logrus.Errorf("error removing %s from digest set: %q", id, err)
|
|
|
|
}
|
2015-11-18 17:18:07 -05:00
|
|
|
delete(is.images, id)
|
2016-09-15 19:37:32 -04:00
|
|
|
is.fs.Delete(id.Digest())
|
2015-11-18 17:18:07 -05:00
|
|
|
|
|
|
|
if imageMeta.layer != nil {
|
|
|
|
return is.ls.Release(imageMeta.layer)
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (is *store) SetParent(id, parent ID) error {
|
|
|
|
is.Lock()
|
|
|
|
defer is.Unlock()
|
|
|
|
parentMeta := is.images[parent]
|
|
|
|
if parentMeta == nil {
|
|
|
|
return fmt.Errorf("unknown parent image ID %s", parent.String())
|
|
|
|
}
|
2016-02-05 20:04:44 -05:00
|
|
|
if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil {
|
|
|
|
delete(is.images[parent].children, id)
|
|
|
|
}
|
2015-11-18 17:18:07 -05:00
|
|
|
parentMeta.children[id] = struct{}{}
|
2016-09-15 19:37:32 -04:00
|
|
|
return is.fs.SetMetadata(id.Digest(), "parent", []byte(parent))
|
2015-11-18 17:18:07 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (is *store) GetParent(id ID) (ID, error) {
|
2016-09-15 19:37:32 -04:00
|
|
|
d, err := is.fs.GetMetadata(id.Digest(), "parent")
|
2015-11-18 17:18:07 -05:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return ID(d), nil // todo: validate?
|
|
|
|
}
|
|
|
|
|
2017-03-02 15:47:02 -05:00
|
|
|
// 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))
|
|
|
|
}
|
|
|
|
|
2015-11-18 17:18:07 -05:00
|
|
|
func (is *store) Children(id ID) []ID {
|
2017-06-20 21:31:29 -04:00
|
|
|
is.RLock()
|
|
|
|
defer is.RUnlock()
|
2015-11-18 17:18:07 -05:00
|
|
|
|
|
|
|
return is.children(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (is *store) children(id ID) []ID {
|
|
|
|
var ids []ID
|
|
|
|
if is.images[id] != nil {
|
|
|
|
for id := range is.images[id].children {
|
|
|
|
ids = append(ids, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ids
|
|
|
|
}
|
|
|
|
|
|
|
|
func (is *store) Heads() map[ID]*Image {
|
|
|
|
return is.imagesMap(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (is *store) Map() map[ID]*Image {
|
|
|
|
return is.imagesMap(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (is *store) imagesMap(all bool) map[ID]*Image {
|
2017-06-20 21:31:29 -04:00
|
|
|
is.RLock()
|
|
|
|
defer is.RUnlock()
|
2015-11-18 17:18:07 -05:00
|
|
|
|
|
|
|
images := make(map[ID]*Image)
|
|
|
|
|
|
|
|
for id := range is.images {
|
|
|
|
if !all && len(is.children(id)) > 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
img, err := is.Get(id)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("invalid image access: %q, error: %q", id, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
images[id] = img
|
|
|
|
}
|
|
|
|
return images
|
|
|
|
}
|