Merge pull request #22866 from jstarks/foreign_layers

Support layers from external URLs
This commit is contained in:
Arnaud Porterie 2016-06-07 00:27:40 +00:00
commit 7d08f3a5ad
21 changed files with 256 additions and 60 deletions

View File

@ -133,6 +133,7 @@ type v2LayerDescriptor struct {
V2MetadataService *metadata.V2MetadataService V2MetadataService *metadata.V2MetadataService
tmpFile *os.File tmpFile *os.File
verifier digest.Verifier verifier digest.Verifier
foreignSrc *distribution.Descriptor
} }
func (ld *v2LayerDescriptor) Key() string { func (ld *v2LayerDescriptor) Key() string {
@ -180,9 +181,8 @@ func (ld *v2LayerDescriptor) Download(ctx context.Context, progressOutput progre
} }
tmpFile := ld.tmpFile tmpFile := ld.tmpFile
blobs := ld.repo.Blobs(ctx)
layerDownload, err := blobs.Open(ctx, ld.digest) layerDownload, err := ld.open(ctx)
if err != nil { if err != nil {
logrus.Errorf("Error initiating layer download: %v", err) logrus.Errorf("Error initiating layer download: %v", err)
if err == distribution.ErrBlobUnknown { if err == distribution.ErrBlobUnknown {
@ -501,6 +501,29 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
return imageID, manifestDigest, nil return imageID, manifestDigest, nil
} }
var descriptors []xfer.DownloadDescriptor
// Note that the order of this loop is in the direction of bottom-most
// to top-most, so that the downloads slice gets ordered correctly.
for _, d := range mfst.Layers {
layerDescriptor := &v2LayerDescriptor{
digest: d.Digest,
repo: p.repo,
repoInfo: p.repoInfo,
V2MetadataService: p.V2MetadataService,
}
if d.MediaType == schema2.MediaTypeForeignLayer && len(d.URLs) > 0 {
if !layer.ForeignSourceSupported() {
return "", "", errors.New("foreign layers are not supported on this OS")
}
layerDescriptor.foreignSrc = &d
}
descriptors = append(descriptors, layerDescriptor)
}
configChan := make(chan []byte, 1) configChan := make(chan []byte, 1)
errChan := make(chan error, 1) errChan := make(chan error, 1)
var cancel func() var cancel func()
@ -517,21 +540,6 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
configChan <- configJSON configChan <- configJSON
}() }()
var descriptors []xfer.DownloadDescriptor
// Note that the order of this loop is in the direction of bottom-most
// to top-most, so that the downloads slice gets ordered correctly.
for _, d := range mfst.References() {
layerDescriptor := &v2LayerDescriptor{
digest: d.Digest,
repo: p.repo,
repoInfo: p.repoInfo,
V2MetadataService: p.V2MetadataService,
}
descriptors = append(descriptors, layerDescriptor)
}
var ( var (
configJSON []byte // raw serialized image config configJSON []byte // raw serialized image config
unmarshalledConfig image.Image // deserialized image config unmarshalledConfig image.Image // deserialized image config

View File

@ -3,6 +3,8 @@
package distribution package distribution
import ( import (
"github.com/docker/distribution"
"github.com/docker/distribution/context"
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
"github.com/docker/docker/image" "github.com/docker/docker/image"
) )
@ -10,3 +12,8 @@ import (
func detectBaseLayer(is image.Store, m *schema1.Manifest, rootFS *image.RootFS) error { func detectBaseLayer(is image.Store, m *schema1.Manifest, rootFS *image.RootFS) error {
return nil return nil
} }
func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekCloser, error) {
blobs := ld.repo.Blobs(ctx)
return blobs.Open(ctx, ld.digest)
}

View File

@ -5,9 +5,15 @@ package distribution
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"os"
"github.com/docker/distribution"
"github.com/docker/distribution/context"
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/image" "github.com/docker/docker/image"
"github.com/docker/docker/layer"
) )
func detectBaseLayer(is image.Store, m *schema1.Manifest, rootFS *image.RootFS) error { func detectBaseLayer(is image.Store, m *schema1.Manifest, rootFS *image.RootFS) error {
@ -28,3 +34,33 @@ func detectBaseLayer(is image.Store, m *schema1.Manifest, rootFS *image.RootFS)
} }
return fmt.Errorf("Invalid base layer %q", v1img.Parent) return fmt.Errorf("Invalid base layer %q", v1img.Parent)
} }
var _ layer.ForeignSourcer = &v2LayerDescriptor{}
func (ld *v2LayerDescriptor) ForeignSource() *distribution.Descriptor {
return ld.foreignSrc
}
func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekCloser, error) {
if ld.foreignSrc == nil {
blobs := ld.repo.Blobs(ctx)
return blobs.Open(ctx, ld.digest)
}
var (
err error
rsc distribution.ReadSeekCloser
)
// Find the first URL that results in a 200 result code.
for _, url := range ld.foreignSrc.URLs {
rsc = transport.NewHTTPReadSeeker(http.DefaultClient, url, nil)
_, err = rsc.Seek(0, os.SEEK_SET)
if err == nil {
break
}
rsc.Close()
rsc = nil
}
return rsc, err
}

View File

@ -240,6 +240,13 @@ func (pd *v2PushDescriptor) DiffID() layer.DiffID {
} }
func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.Output) (distribution.Descriptor, error) { func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.Output) (distribution.Descriptor, error) {
if fs, ok := pd.layer.(layer.ForeignSourcer); ok {
if d := fs.ForeignSource(); d != nil {
progress.Update(progressOutput, pd.ID(), "Skipped foreign layer")
return *d, nil
}
}
diffID := pd.DiffID() diffID := pd.DiffID()
pd.pushState.Lock() pd.pushState.Lock()

View File

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/distribution"
"github.com/docker/docker/image" "github.com/docker/docker/image"
"github.com/docker/docker/layer" "github.com/docker/docker/layer"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
@ -318,7 +319,11 @@ func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor,
return return
} }
d.layer, err = d.layerStore.Register(inflatedLayerData, parentLayer) var src *distribution.Descriptor
if fs, ok := descriptor.(layer.ForeignSourcer); ok {
src = fs.ForeignSource()
}
d.layer, err = d.layerStore.RegisterForeign(inflatedLayerData, parentLayer, src)
if err != nil { if err != nil {
select { select {
case <-d.Transfer.Context().Done(): case <-d.Transfer.Context().Done():
@ -409,7 +414,11 @@ func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor Downloa
} }
defer layerReader.Close() defer layerReader.Close()
d.layer, err = d.layerStore.Register(layerReader, parentLayer) var src *distribution.Descriptor
if fs, ok := l.(layer.ForeignSourcer); ok {
src = fs.ForeignSource()
}
d.layer, err = d.layerStore.RegisterForeign(layerReader, parentLayer, src)
if err != nil { if err != nil {
d.err = fmt.Errorf("failed to register layer: %v", err) d.err = fmt.Errorf("failed to register layer: %v", err)
return return

View File

@ -10,6 +10,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/docker/distribution"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/docker/image" "github.com/docker/docker/image"
"github.com/docker/docker/layer" "github.com/docker/docker/layer"
@ -71,6 +72,10 @@ func createChainIDFromParent(parent layer.ChainID, dgsts ...layer.DiffID) layer.
} }
func (ls *mockLayerStore) Register(reader io.Reader, parentID layer.ChainID) (layer.Layer, error) { func (ls *mockLayerStore) Register(reader io.Reader, parentID layer.ChainID) (layer.Layer, error) {
return ls.RegisterForeign(reader, parentID, nil)
}
func (ls *mockLayerStore) RegisterForeign(reader io.Reader, parentID layer.ChainID, _ *distribution.Descriptor) (layer.Layer, error) {
var ( var (
parent layer.Layer parent layer.Layer
err error err error

View File

@ -87,7 +87,7 @@ clone git github.com/boltdb/bolt v1.2.1
clone git github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7 clone git github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7
# get graph and distribution packages # get graph and distribution packages
clone git github.com/docker/distribution 9ec0d742d69f77caa4dd5f49ceb70c3067d39f30 clone git github.com/docker/distribution 5bbf65499960b184fe8e0f045397375e1a6722b8
clone git github.com/vbatts/tar-split v0.9.11 clone git github.com/vbatts/tar-split v0.9.11
# get go-zfs packages # get go-zfs packages

View File

@ -10,6 +10,7 @@ import (
"reflect" "reflect"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/distribution"
"github.com/docker/docker/image" "github.com/docker/docker/image"
"github.com/docker/docker/image/v1" "github.com/docker/docker/image/v1"
"github.com/docker/docker/layer" "github.com/docker/docker/layer"
@ -63,6 +64,10 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool)
var parentLinks []parentLink var parentLinks []parentLink
for _, m := range manifest { for _, m := range manifest {
if m.LayerSources != nil && !layer.ForeignSourceSupported() {
return fmt.Errorf("invalid manifest, foreign layers not supported on this operating system")
}
configPath, err := safePath(tmpDir, m.Config) configPath, err := safePath(tmpDir, m.Config)
if err != nil { if err != nil {
return err return err
@ -92,7 +97,7 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool)
r.Append(diffID) r.Append(diffID)
newLayer, err := l.ls.Get(r.ChainID()) newLayer, err := l.ls.Get(r.ChainID())
if err != nil { if err != nil {
newLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), progressOutput) newLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), m.LayerSources[diffID], progressOutput)
if err != nil { if err != nil {
return err return err
} }
@ -151,7 +156,7 @@ func (l *tarexporter) setParentID(id, parentID image.ID) error {
return l.is.SetParent(id, parentID) return l.is.SetParent(id, parentID)
} }
func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, progressOutput progress.Output) (layer.Layer, error) { func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, foreignSrc *distribution.Descriptor, progressOutput progress.Output) (layer.Layer, error) {
rawTar, err := os.Open(filename) rawTar, err := os.Open(filename)
if err != nil { if err != nil {
logrus.Debugf("Error reading embedded tar: %v", err) logrus.Debugf("Error reading embedded tar: %v", err)
@ -174,9 +179,9 @@ func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string,
progressReader := progress.NewProgressReader(inflatedLayerData, progressOutput, fileInfo.Size(), stringid.TruncateID(id), "Loading layer") progressReader := progress.NewProgressReader(inflatedLayerData, progressOutput, fileInfo.Size(), stringid.TruncateID(id), "Loading layer")
return l.ls.Register(progressReader, rootFS.ChainID()) return l.ls.RegisterForeign(progressReader, rootFS.ChainID(), foreignSrc)
} }
return l.ls.Register(inflatedLayerData, rootFS.ChainID()) return l.ls.RegisterForeign(inflatedLayerData, rootFS.ChainID(), foreignSrc)
} }
func (l *tarexporter) setLoadedTag(ref reference.NamedTagged, imgID image.ID, outStream io.Writer) error { func (l *tarexporter) setLoadedTag(ref reference.NamedTagged, imgID image.ID, outStream io.Writer) error {
@ -298,7 +303,7 @@ func (l *tarexporter) legacyLoadImage(oldID, sourceDir string, loadedMap map[str
if err != nil { if err != nil {
return err return err
} }
newLayer, err := l.loadLayer(layerPath, *rootFS, oldID, progressOutput) newLayer, err := l.loadLayer(layerPath, *rootFS, oldID, nil, progressOutput)
if err != nil { if err != nil {
return err return err
} }

View File

@ -9,6 +9,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/docker/distribution"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/docker/image" "github.com/docker/docker/image"
"github.com/docker/docker/image/v1" "github.com/docker/docker/image/v1"
@ -131,7 +132,8 @@ func (s *saveSession) save(outStream io.Writer) error {
var parentLinks []parentLink var parentLinks []parentLink
for id, imageDescr := range s.images { for id, imageDescr := range s.images {
if err = s.saveImage(id); err != nil { foreignSrcs, err := s.saveImage(id)
if err != nil {
return err return err
} }
@ -151,9 +153,10 @@ func (s *saveSession) save(outStream io.Writer) error {
} }
manifest = append(manifest, manifestItem{ manifest = append(manifest, manifestItem{
Config: digest.Digest(id).Hex() + ".json", Config: digest.Digest(id).Hex() + ".json",
RepoTags: repoTags, RepoTags: repoTags,
Layers: layers, Layers: layers,
LayerSources: foreignSrcs,
}) })
parentID, _ := s.is.GetParent(id) parentID, _ := s.is.GetParent(id)
@ -213,18 +216,19 @@ func (s *saveSession) save(outStream io.Writer) error {
return nil return nil
} }
func (s *saveSession) saveImage(id image.ID) error { func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]*distribution.Descriptor, error) {
img, err := s.is.Get(id) img, err := s.is.Get(id)
if err != nil { if err != nil {
return err return nil, err
} }
if len(img.RootFS.DiffIDs) == 0 { if len(img.RootFS.DiffIDs) == 0 {
return fmt.Errorf("empty export - not implemented") return nil, fmt.Errorf("empty export - not implemented")
} }
var parent digest.Digest var parent digest.Digest
var layers []string var layers []string
var foreignSrcs map[layer.DiffID]*distribution.Descriptor
for i := range img.RootFS.DiffIDs { for i := range img.RootFS.DiffIDs {
v1Img := image.V1Image{} v1Img := image.V1Image{}
if i == len(img.RootFS.DiffIDs)-1 { if i == len(img.RootFS.DiffIDs)-1 {
@ -234,7 +238,7 @@ func (s *saveSession) saveImage(id image.ID) error {
rootFS.DiffIDs = rootFS.DiffIDs[:i+1] rootFS.DiffIDs = rootFS.DiffIDs[:i+1]
v1ID, err := v1.CreateID(v1Img, rootFS.ChainID(), parent) v1ID, err := v1.CreateID(v1Img, rootFS.ChainID(), parent)
if err != nil { if err != nil {
return err return nil, err
} }
v1Img.ID = v1ID.Hex() v1Img.ID = v1ID.Hex()
@ -242,79 +246,91 @@ func (s *saveSession) saveImage(id image.ID) error {
v1Img.Parent = parent.Hex() v1Img.Parent = parent.Hex()
} }
if err := s.saveLayer(rootFS.ChainID(), v1Img, img.Created); err != nil { src, err := s.saveLayer(rootFS.ChainID(), v1Img, img.Created)
return err if err != nil {
return nil, err
} }
layers = append(layers, v1Img.ID) layers = append(layers, v1Img.ID)
parent = v1ID parent = v1ID
if src != nil {
if foreignSrcs == nil {
foreignSrcs = make(map[layer.DiffID]*distribution.Descriptor)
}
foreignSrcs[img.RootFS.DiffIDs[i]] = src
}
} }
configFile := filepath.Join(s.outDir, digest.Digest(id).Hex()+".json") configFile := filepath.Join(s.outDir, digest.Digest(id).Hex()+".json")
if err := ioutil.WriteFile(configFile, img.RawJSON(), 0644); err != nil { if err := ioutil.WriteFile(configFile, img.RawJSON(), 0644); err != nil {
return err return nil, err
} }
if err := system.Chtimes(configFile, img.Created, img.Created); err != nil { if err := system.Chtimes(configFile, img.Created, img.Created); err != nil {
return err return nil, err
} }
s.images[id].layers = layers s.images[id].layers = layers
return nil return foreignSrcs, nil
} }
func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, createdTime time.Time) error { func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, createdTime time.Time) (*distribution.Descriptor, error) {
if _, exists := s.savedLayers[legacyImg.ID]; exists { if _, exists := s.savedLayers[legacyImg.ID]; exists {
return nil return nil, nil
} }
outDir := filepath.Join(s.outDir, legacyImg.ID) outDir := filepath.Join(s.outDir, legacyImg.ID)
if err := os.Mkdir(outDir, 0755); err != nil { if err := os.Mkdir(outDir, 0755); err != nil {
return err return nil, err
} }
// todo: why is this version file here? // todo: why is this version file here?
if err := ioutil.WriteFile(filepath.Join(outDir, legacyVersionFileName), []byte("1.0"), 0644); err != nil { if err := ioutil.WriteFile(filepath.Join(outDir, legacyVersionFileName), []byte("1.0"), 0644); err != nil {
return err return nil, err
} }
imageConfig, err := json.Marshal(legacyImg) imageConfig, err := json.Marshal(legacyImg)
if err != nil { if err != nil {
return err return nil, err
} }
if err := ioutil.WriteFile(filepath.Join(outDir, legacyConfigFileName), imageConfig, 0644); err != nil { if err := ioutil.WriteFile(filepath.Join(outDir, legacyConfigFileName), imageConfig, 0644); err != nil {
return err return nil, err
} }
// serialize filesystem // serialize filesystem
tarFile, err := os.Create(filepath.Join(outDir, legacyLayerFileName)) tarFile, err := os.Create(filepath.Join(outDir, legacyLayerFileName))
if err != nil { if err != nil {
return err return nil, err
} }
defer tarFile.Close() defer tarFile.Close()
l, err := s.ls.Get(id) l, err := s.ls.Get(id)
if err != nil { if err != nil {
return err return nil, err
} }
defer layer.ReleaseAndLog(s.ls, l) defer layer.ReleaseAndLog(s.ls, l)
arch, err := l.TarStream() arch, err := l.TarStream()
if err != nil { if err != nil {
return err return nil, err
} }
defer arch.Close() defer arch.Close()
if _, err := io.Copy(tarFile, arch); err != nil { if _, err := io.Copy(tarFile, arch); err != nil {
return err return nil, err
} }
for _, fname := range []string{"", legacyVersionFileName, legacyConfigFileName, legacyLayerFileName} { for _, fname := range []string{"", legacyVersionFileName, legacyConfigFileName, legacyLayerFileName} {
// todo: maybe save layer created timestamp? // todo: maybe save layer created timestamp?
if err := system.Chtimes(filepath.Join(outDir, fname), createdTime, createdTime); err != nil { if err := system.Chtimes(filepath.Join(outDir, fname), createdTime, createdTime); err != nil {
return err return nil, err
} }
} }
s.savedLayers[legacyImg.ID] = struct{}{} s.savedLayers[legacyImg.ID] = struct{}{}
return nil
var src *distribution.Descriptor
if fs, ok := l.(layer.ForeignSourcer); ok {
src = fs.ForeignSource()
}
return src, nil
} }

View File

@ -1,6 +1,7 @@
package tarexport package tarexport
import ( import (
"github.com/docker/distribution"
"github.com/docker/docker/image" "github.com/docker/docker/image"
"github.com/docker/docker/layer" "github.com/docker/docker/layer"
"github.com/docker/docker/reference" "github.com/docker/docker/reference"
@ -15,10 +16,11 @@ const (
) )
type manifestItem struct { type manifestItem struct {
Config string Config string
RepoTags []string RepoTags []string
Layers []string Layers []string
Parent image.ID `json:",omitempty"` Parent image.ID `json:",omitempty"`
LayerSources map[layer.DiffID]*distribution.Descriptor `json:",omitempty"`
} }
type tarexporter struct { type tarexporter struct {

View File

@ -2,6 +2,7 @@ package layer
import ( import (
"compress/gzip" "compress/gzip"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -13,6 +14,7 @@ import (
"strings" "strings"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/distribution"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/ioutils"
) )
@ -24,6 +26,9 @@ var (
// digest.SHA384, // Currently not used // digest.SHA384, // Currently not used
// digest.SHA512, // Currently not used // digest.SHA512, // Currently not used
} }
// ErrNoForeignSource is returned when no foreign source is set for a layer.
ErrNoForeignSource = errors.New("layer does not have a foreign source")
) )
type fileMetadataStore struct { type fileMetadataStore struct {
@ -98,6 +103,14 @@ func (fm *fileMetadataTransaction) SetCacheID(cacheID string) error {
return ioutil.WriteFile(filepath.Join(fm.root, "cache-id"), []byte(cacheID), 0644) return ioutil.WriteFile(filepath.Join(fm.root, "cache-id"), []byte(cacheID), 0644)
} }
func (fm *fileMetadataTransaction) SetForeignSource(ref distribution.Descriptor) error {
jsonRef, err := json.Marshal(ref)
if err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(fm.root, "descriptor.json"), jsonRef, 0644)
}
func (fm *fileMetadataTransaction) TarSplitWriter(compressInput bool) (io.WriteCloser, error) { func (fm *fileMetadataTransaction) TarSplitWriter(compressInput bool) (io.WriteCloser, error) {
f, err := os.OpenFile(filepath.Join(fm.root, "tar-split.json.gz"), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644) f, err := os.OpenFile(filepath.Join(fm.root, "tar-split.json.gz"), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
@ -191,6 +204,23 @@ func (fms *fileMetadataStore) GetCacheID(layer ChainID) (string, error) {
return content, nil return content, nil
} }
func (fms *fileMetadataStore) GetForeignSource(layer ChainID) (distribution.Descriptor, error) {
content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "descriptor.json"))
if err != nil {
if os.IsNotExist(err) {
return distribution.Descriptor{}, ErrNoForeignSource
}
return distribution.Descriptor{}, err
}
var ref distribution.Descriptor
err = json.Unmarshal(content, &ref)
if err != nil {
return distribution.Descriptor{}, err
}
return ref, err
}
func (fms *fileMetadataStore) TarSplitReader(layer ChainID) (io.ReadCloser, error) { func (fms *fileMetadataStore) TarSplitReader(layer ChainID) (io.ReadCloser, error) {
fz, err := os.Open(fms.getLayerFilename(layer, "tar-split.json.gz")) fz, err := os.Open(fms.getLayerFilename(layer, "tar-split.json.gz"))
if err != nil { if err != nil {

View File

@ -14,6 +14,7 @@ import (
"io" "io"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/distribution"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
) )
@ -107,6 +108,14 @@ type Layer interface {
Metadata() (map[string]string, error) Metadata() (map[string]string, error)
} }
// ForeignSourcer is an interface used to describe the source of layers
// and objects representing layers, when the source is a foreign URL.
type ForeignSourcer interface {
// ForeignSource returns the descriptor for this layer if it is
// a foreign layer, or nil for ordinary layers.
ForeignSource() *distribution.Descriptor
}
// RWLayer represents a layer which is // RWLayer represents a layer which is
// read and writable // read and writable
type RWLayer interface { type RWLayer interface {
@ -168,6 +177,7 @@ type MountInit func(root string) error
// read-only and read-write layers. // read-only and read-write layers.
type Store interface { type Store interface {
Register(io.Reader, ChainID) (Layer, error) Register(io.Reader, ChainID) (Layer, error)
RegisterForeign(io.Reader, ChainID, *distribution.Descriptor) (Layer, error)
Get(ChainID) (Layer, error) Get(ChainID) (Layer, error)
Release(Layer) ([]Metadata, error) Release(Layer) ([]Metadata, error)
@ -188,6 +198,7 @@ type MetadataTransaction interface {
SetParent(parent ChainID) error SetParent(parent ChainID) error
SetDiffID(DiffID) error SetDiffID(DiffID) error
SetCacheID(string) error SetCacheID(string) error
SetForeignSource(distribution.Descriptor) error
TarSplitWriter(compressInput bool) (io.WriteCloser, error) TarSplitWriter(compressInput bool) (io.WriteCloser, error)
Commit(ChainID) error Commit(ChainID) error
@ -207,6 +218,7 @@ type MetadataStore interface {
GetParent(ChainID) (ChainID, error) GetParent(ChainID) (ChainID, error)
GetDiffID(ChainID) (DiffID, error) GetDiffID(ChainID) (DiffID, error)
GetCacheID(ChainID) (string, error) GetCacheID(ChainID) (string, error)
GetForeignSource(ChainID) (distribution.Descriptor, error)
TarSplitReader(ChainID) (io.ReadCloser, error) TarSplitReader(ChainID) (io.ReadCloser, error)
SetMountID(string, string) error SetMountID(string, string) error

View File

@ -8,6 +8,7 @@ import (
"sync" "sync"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/distribution"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
@ -137,6 +138,13 @@ func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) {
references: map[Layer]struct{}{}, references: map[Layer]struct{}{},
} }
foreignSrc, err := ls.store.GetForeignSource(layer)
if err == nil {
cl.foreignSrc = &foreignSrc
} else if err != ErrNoForeignSource {
return nil, fmt.Errorf("failed to get foreign reference for %s: %s", layer, err)
}
if parent != "" { if parent != "" {
p, err := ls.loadLayer(parent) p, err := ls.loadLayer(parent)
if err != nil { if err != nil {
@ -228,6 +236,10 @@ func (ls *layerStore) applyTar(tx MetadataTransaction, ts io.Reader, parent stri
} }
func (ls *layerStore) Register(ts io.Reader, parent ChainID) (Layer, error) { func (ls *layerStore) Register(ts io.Reader, parent ChainID) (Layer, error) {
return ls.RegisterForeign(ts, parent, nil)
}
func (ls *layerStore) RegisterForeign(ts io.Reader, parent ChainID, foreignSrc *distribution.Descriptor) (Layer, error) {
// err is used to hold the error which will always trigger // err is used to hold the error which will always trigger
// cleanup of creates sources but may not be an error returned // cleanup of creates sources but may not be an error returned
// to the caller (already exists). // to the caller (already exists).
@ -258,6 +270,7 @@ func (ls *layerStore) Register(ts io.Reader, parent ChainID) (Layer, error) {
layer := &roLayer{ layer := &roLayer{
parent: p, parent: p,
cacheID: stringid.GenerateRandomID(), cacheID: stringid.GenerateRandomID(),
foreignSrc: foreignSrc,
referenceCount: 1, referenceCount: 1,
layerStore: ls, layerStore: ls,
references: map[Layer]struct{}{}, references: map[Layer]struct{}{},

View File

@ -7,3 +7,9 @@ import "github.com/docker/docker/pkg/stringid"
func (ls *layerStore) mountID(name string) string { func (ls *layerStore) mountID(name string) string {
return stringid.GenerateRandomID() return stringid.GenerateRandomID()
} }
// ForeignSourceSupported returns whether layers downloaded from foreign sources are
// supported in this daemon.
func ForeignSourceSupported() bool {
return false
}

View File

@ -96,3 +96,9 @@ func (ls *layerStore) mountID(name string) string {
func (ls *layerStore) GraphDriver() graphdriver.Driver { func (ls *layerStore) GraphDriver() graphdriver.Driver {
return ls.driver return ls.driver
} }
// ForeignSourceSupported returns whether layers downloaded from foreign sources are
// supported in this daemon.
func ForeignSourceSupported() bool {
return true
}

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/docker/distribution"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
) )
@ -14,6 +15,7 @@ type roLayer struct {
cacheID string cacheID string
size int64 size int64
layerStore *layerStore layerStore *layerStore
foreignSrc *distribution.Descriptor
referenceCount int referenceCount int
references map[Layer]struct{} references map[Layer]struct{}
@ -123,6 +125,11 @@ func storeLayer(tx MetadataTransaction, layer *roLayer) error {
return err return err
} }
} }
if layer.foreignSrc != nil {
if err := tx.SetForeignSource(*layer.foreignSrc); err != nil {
return err
}
}
return nil return nil
} }

View File

@ -0,0 +1,9 @@
package layer
import "github.com/docker/distribution"
var _ ForeignSourcer = &roLayer{}
func (rl *roLayer) ForeignSource() *distribution.Descriptor {
return rl.foreignSrc
}

View File

@ -1,4 +1,5 @@
Aaron Lehmann <aaron.lehmann@docker.com> Aaron Lehmann <aaron.lehmann@docker.com>
Aaron Schlesinger <aschlesinger@deis.com>
Aaron Vinson <avinson.public@gmail.com> Aaron Vinson <avinson.public@gmail.com>
Adam Enger <adamenger@gmail.com> Adam Enger <adamenger@gmail.com>
Adrian Mouat <adrian.mouat@gmail.com> Adrian Mouat <adrian.mouat@gmail.com>
@ -7,13 +8,16 @@ Alex Chan <alex.chan@metaswitch.com>
Alex Elman <aelman@indeed.com> Alex Elman <aelman@indeed.com>
amitshukla <ashukla73@hotmail.com> amitshukla <ashukla73@hotmail.com>
Amy Lindburg <amy.lindburg@docker.com> Amy Lindburg <amy.lindburg@docker.com>
Andrew Hsu <andrewhsu@acm.org>
Andrew Meredith <andymeredith@gmail.com> Andrew Meredith <andymeredith@gmail.com>
Andrew T Nguyen <andrew.nguyen@docker.com> Andrew T Nguyen <andrew.nguyen@docker.com>
Andrey Kostov <kostov.andrey@gmail.com> Andrey Kostov <kostov.andrey@gmail.com>
Andy Goldstein <agoldste@redhat.com> Andy Goldstein <agoldste@redhat.com>
Anis Elleuch <vadmeste@gmail.com>
Anton Tiurin <noxiouz@yandex.ru> Anton Tiurin <noxiouz@yandex.ru>
Antonio Mercado <amercado@thinknode.com> Antonio Mercado <amercado@thinknode.com>
Antonio Murdaca <runcom@redhat.com> Antonio Murdaca <runcom@redhat.com>
Arien Holthuizen <aholthuizen@schubergphilis.com>
Arnaud Porterie <arnaud.porterie@docker.com> Arnaud Porterie <arnaud.porterie@docker.com>
Arthur Baars <arthur@semmle.com> Arthur Baars <arthur@semmle.com>
Asuka Suzuki <hello@tanksuzuki.com> Asuka Suzuki <hello@tanksuzuki.com>
@ -27,6 +31,7 @@ burnettk <burnettk@gmail.com>
Carson A <ca@carsonoid.net> Carson A <ca@carsonoid.net>
Chris Dillon <squarism@gmail.com> Chris Dillon <squarism@gmail.com>
Daisuke Fujita <dtanshi45@gmail.com> Daisuke Fujita <dtanshi45@gmail.com>
Daniel Huhn <daniel@danielhuhn.de>
Darren Shepherd <darren@rancher.com> Darren Shepherd <darren@rancher.com>
Dave Trombley <dave.trombley@gmail.com> Dave Trombley <dave.trombley@gmail.com>
Dave Tucker <dt@docker.com> Dave Tucker <dt@docker.com>
@ -41,6 +46,7 @@ DJ Enriquez <dj.enriquez@infospace.com>
Donald Huang <don.hcd@gmail.com> Donald Huang <don.hcd@gmail.com>
Doug Davis <dug@us.ibm.com> Doug Davis <dug@us.ibm.com>
Eric Yang <windfarer@gmail.com> Eric Yang <windfarer@gmail.com>
Fabio Huser <fabio@fh1.ch>
farmerworking <farmerworking@gmail.com> farmerworking <farmerworking@gmail.com>
Felix Yan <felixonmars@archlinux.org> Felix Yan <felixonmars@archlinux.org>
Florentin Raud <florentin.raud@gmail.com> Florentin Raud <florentin.raud@gmail.com>
@ -57,8 +63,10 @@ Jack Griffin <jackpg14@gmail.com>
Jason Freidman <jason.freidman@gmail.com> Jason Freidman <jason.freidman@gmail.com>
Jeff Nickoloff <jeff@allingeek.com> Jeff Nickoloff <jeff@allingeek.com>
Jessie Frazelle <jessie@docker.com> Jessie Frazelle <jessie@docker.com>
jhaohai <jhaohai@foxmail.com>
Jianqing Wang <tsing@jianqing.org> Jianqing Wang <tsing@jianqing.org>
John Starks <jostarks@microsoft.com> John Starks <jostarks@microsoft.com>
Jon Johnson <jonjohnson@google.com>
Jon Poler <jonathan.poler@apcera.com> Jon Poler <jonathan.poler@apcera.com>
Jonathan Boulle <jonathanboulle@gmail.com> Jonathan Boulle <jonathanboulle@gmail.com>
Jordan Liggitt <jliggitt@redhat.com> Jordan Liggitt <jliggitt@redhat.com>
@ -92,17 +100,20 @@ Olivier Gambier <olivier@docker.com>
Olivier Jacques <olivier.jacques@hp.com> Olivier Jacques <olivier.jacques@hp.com>
Omer Cohen <git@omer.io> Omer Cohen <git@omer.io>
Patrick Devine <patrick.devine@docker.com> Patrick Devine <patrick.devine@docker.com>
Phil Estes <estesp@linux.vnet.ibm.com>
Philip Misiowiec <philip@atlashealth.com> Philip Misiowiec <philip@atlashealth.com>
Richard Scothern <richard.scothern@docker.com> Richard Scothern <richard.scothern@docker.com>
Rodolfo Carvalho <rhcarvalho@gmail.com> Rodolfo Carvalho <rhcarvalho@gmail.com>
Rusty Conover <rusty@luckydinosaur.com> Rusty Conover <rusty@luckydinosaur.com>
Sean Boran <Boran@users.noreply.github.com> Sean Boran <Boran@users.noreply.github.com>
Sebastiaan van Stijn <github@gone.nl> Sebastiaan van Stijn <github@gone.nl>
Serge Dubrouski <sergeyfd@gmail.com>
Sharif Nassar <sharif@mrwacky.com> Sharif Nassar <sharif@mrwacky.com>
Shawn Falkner-Horine <dreadpirateshawn@gmail.com> Shawn Falkner-Horine <dreadpirateshawn@gmail.com>
Shreyas Karnik <karnik.shreyas@gmail.com> Shreyas Karnik <karnik.shreyas@gmail.com>
Simon Thulbourn <simon+github@thulbourn.com> Simon Thulbourn <simon+github@thulbourn.com>
Spencer Rinehart <anubis@overthemonkey.com> Spencer Rinehart <anubis@overthemonkey.com>
Stefan Majewsky <stefan.majewsky@sap.com>
Stefan Weil <sw@weilnetz.de> Stefan Weil <sw@weilnetz.de>
Stephen J Day <stephen.day@docker.com> Stephen J Day <stephen.day@docker.com>
Sungho Moon <sungho.moon@navercorp.com> Sungho Moon <sungho.moon@navercorp.com>
@ -114,6 +125,7 @@ Thomas Sjögren <konstruktoid@users.noreply.github.com>
Tianon Gravi <admwiggin@gmail.com> Tianon Gravi <admwiggin@gmail.com>
Tibor Vass <teabee89@gmail.com> Tibor Vass <teabee89@gmail.com>
Tonis Tiigi <tonistiigi@gmail.com> Tonis Tiigi <tonistiigi@gmail.com>
Tony Holdstock-Brown <tony@docker.com>
Trevor Pounds <trevor.pounds@gmail.com> Trevor Pounds <trevor.pounds@gmail.com>
Troels Thomsen <troels@thomsen.io> Troels Thomsen <troels@thomsen.io>
Vincent Batts <vbatts@redhat.com> Vincent Batts <vbatts@redhat.com>

View File

@ -1,8 +1,4 @@
FROM golang:1.6 FROM golang:1.6-alpine
RUN apt-get update && \
apt-get install -y apache2-utils && \
rm -rf /var/lib/apt/lists/*
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
ENV DOCKER_BUILDTAGS include_oss include_gcs ENV DOCKER_BUILDTAGS include_oss include_gcs
@ -10,6 +6,10 @@ ENV DOCKER_BUILDTAGS include_oss include_gcs
WORKDIR $DISTRIBUTION_DIR WORKDIR $DISTRIBUTION_DIR
COPY . $DISTRIBUTION_DIR COPY . $DISTRIBUTION_DIR
COPY cmd/registry/config-dev.yml /etc/docker/registry/config.yml COPY cmd/registry/config-dev.yml /etc/docker/registry/config.yml
RUN set -ex \
&& apk add --no-cache make git
RUN make PREFIX=/go clean binaries RUN make PREFIX=/go clean binaries
VOLUME ["/var/lib/registry"] VOLUME ["/var/lib/registry"]

View File

@ -69,6 +69,9 @@ type Descriptor struct {
// against against this digest. // against against this digest.
Digest digest.Digest `json:"digest,omitempty"` Digest digest.Digest `json:"digest,omitempty"`
// URLs contains the source URLs of this content.
URLs []string `json:"urls,omitempty"`
// NOTE: Before adding a field here, please ensure that all // NOTE: Before adding a field here, please ensure that all
// other options have been exhausted. Much of the type relationships // other options have been exhausted. Much of the type relationships
// depend on the simplicity of this type. // depend on the simplicity of this type.

View File

@ -20,6 +20,10 @@ const (
// MediaTypeLayer is the mediaType used for layers referenced by the // MediaTypeLayer is the mediaType used for layers referenced by the
// manifest. // manifest.
MediaTypeLayer = "application/vnd.docker.image.rootfs.diff.tar.gzip" MediaTypeLayer = "application/vnd.docker.image.rootfs.diff.tar.gzip"
// MediaTypeForeignLayer is the mediaType used for layers that must be
// downloaded from foreign URLs.
MediaTypeForeignLayer = "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip"
) )
var ( var (
@ -63,7 +67,6 @@ type Manifest struct {
// References returnes the descriptors of this manifests references. // References returnes the descriptors of this manifests references.
func (m Manifest) References() []distribution.Descriptor { func (m Manifest) References() []distribution.Descriptor {
return m.Layers return m.Layers
} }
// Target returns the target of this signed manifest. // Target returns the target of this signed manifest.