01ba0a93 |
package image
import (
"encoding/json"
"fmt"
"sync" |
016eea00 |
"time" |
01ba0a93 |
|
7a855799 |
"github.com/docker/distribution/digestset" |
01ba0a93 |
"github.com/docker/docker/layer" |
0cba7740 |
"github.com/docker/docker/pkg/system" |
7a855799 |
"github.com/opencontainers/go-digest" |
51360965 |
"github.com/pkg/errors" |
1009e6a4 |
"github.com/sirupsen/logrus" |
01ba0a93 |
)
// 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) |
016eea00 |
SetLastUpdated(id ID) error
GetLastUpdated(id ID) (time.Time, error) |
01ba0a93 |
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 { |
44e67ada |
sync.RWMutex |
afd305c4 |
lss map[string]LayerGetReleaser |
01ba0a93 |
images map[ID]*imageMeta
fs StoreBackend |
7a855799 |
digestSet *digestset.Set |
01ba0a93 |
}
|
afd305c4 |
// NewImageStore returns new store object for given set of layer stores
func NewImageStore(fs StoreBackend, lss map[string]LayerGetReleaser) (Store, error) { |
01ba0a93 |
is := &store{ |
afd305c4 |
lss: lss, |
01ba0a93 |
images: make(map[ID]*imageMeta),
fs: fs, |
7a855799 |
digestSet: digestset.NewSet(), |
01ba0a93 |
}
// load all current images and retain layers
if err := is.restore(); err != nil {
return nil, err
}
return is, nil
}
func (is *store) restore() error { |
80522398 |
err := is.fs.Walk(func(dgst digest.Digest) error {
img, err := is.Get(IDFromDigest(dgst)) |
01ba0a93 |
if err != nil { |
80522398 |
logrus.Errorf("invalid image %v, %v", dgst, err) |
01ba0a93 |
return nil
}
var l layer.Layer
if chainID := img.RootFS.ChainID(); chainID != "" { |
0cba7740 |
if !system.IsOSSupported(img.OperatingSystem()) {
return system.ErrNotSupportedOperatingSystem
} |
afd305c4 |
l, err = is.lss[img.OperatingSystem()].Get(chainID) |
01ba0a93 |
if err != nil {
return err
}
} |
80522398 |
if err := is.digestSet.Add(dgst); err != nil { |
01ba0a93 |
return err
}
imageMeta := &imageMeta{
layer: l,
children: make(map[ID]struct{}),
}
|
80522398 |
is.images[IDFromDigest(dgst)] = imageMeta |
01ba0a93 |
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
}
// 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
} |
80522398 |
imageID := IDFromDigest(dgst) |
01ba0a93 |
is.Lock()
defer is.Unlock()
if _, exists := is.images[imageID]; exists {
return imageID, nil
}
layerID := img.RootFS.ChainID()
var l layer.Layer
if layerID != "" { |
0cba7740 |
if !system.IsOSSupported(img.OperatingSystem()) {
return "", system.ErrNotSupportedOperatingSystem
} |
afd305c4 |
l, err = is.lss[img.OperatingSystem()].Get(layerID) |
01ba0a93 |
if err != nil { |
51360965 |
return "", errors.Wrapf(err, "failed to get layer %s", layerID) |
01ba0a93 |
}
}
imageMeta := &imageMeta{
layer: l,
children: make(map[ID]struct{}),
}
is.images[imageID] = imageMeta |
80522398 |
if err := is.digestSet.Add(imageID.Digest()); err != nil { |
01ba0a93 |
delete(is.images, imageID)
return "", err
}
return imageID, nil
}
|
ebcb7d6b |
type imageNotFoundError string
func (e imageNotFoundError) Error() string {
return "No such image: " + string(e)
}
func (imageNotFoundError) NotFound() {}
|
01ba0a93 |
func (is *store) Search(term string) (ID, error) {
dgst, err := is.digestSet.Lookup(term)
if err != nil { |
7a855799 |
if err == digestset.ErrDigestNotFound { |
ebcb7d6b |
err = imageNotFoundError(term) |
ed231d40 |
} |
ebcb7d6b |
return "", errors.WithStack(err) |
01ba0a93 |
} |
80522398 |
return IDFromDigest(dgst), nil |
01ba0a93 |
}
func (is *store) Get(id ID) (*Image, error) {
// todo: Check if image is in images
// todo: Detect manual insertions and start using them |
80522398 |
config, err := is.fs.Get(id.Digest()) |
01ba0a93 |
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())
} |
c94d34f7 |
img, err := is.Get(id)
if err != nil {
return nil, fmt.Errorf("unrecognized image %s, %v", id.String(), err)
}
if !system.IsOSSupported(img.OperatingSystem()) {
return nil, fmt.Errorf("unsupported image operating system %q", img.OperatingSystem())
} |
01ba0a93 |
for id := range imageMeta.children { |
80522398 |
is.fs.DeleteMetadata(id.Digest(), "parent") |
01ba0a93 |
}
if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil {
delete(is.images[parent].children, id)
}
|
80522398 |
if err := is.digestSet.Remove(id.Digest()); err != nil { |
fcb083c6 |
logrus.Errorf("error removing %s from digest set: %q", id, err)
} |
01ba0a93 |
delete(is.images, id) |
80522398 |
is.fs.Delete(id.Digest()) |
01ba0a93 |
if imageMeta.layer != nil { |
c94d34f7 |
return is.lss[img.OperatingSystem()].Release(imageMeta.layer) |
01ba0a93 |
}
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())
} |
48529324 |
if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil {
delete(is.images[parent].children, id)
} |
01ba0a93 |
parentMeta.children[id] = struct{}{} |
80522398 |
return is.fs.SetMetadata(id.Digest(), "parent", []byte(parent)) |
01ba0a93 |
}
func (is *store) GetParent(id ID) (ID, error) { |
80522398 |
d, err := is.fs.GetMetadata(id.Digest(), "parent") |
01ba0a93 |
if err != nil {
return "", err
}
return ID(d), nil // todo: validate?
}
|
016eea00 |
// 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))
}
|
01ba0a93 |
func (is *store) Children(id ID) []ID { |
44e67ada |
is.RLock()
defer is.RUnlock() |
01ba0a93 |
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 { |
44e67ada |
is.RLock()
defer is.RUnlock() |
01ba0a93 |
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
} |