add better generate
Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
parent
3fc6abf56b
commit
cdd93563f5
5655 changed files with 1187011 additions and 392 deletions
61
vendor/github.com/docker/docker-ce/components/engine/layer/empty.go
generated
vendored
Normal file
61
vendor/github.com/docker/docker-ce/components/engine/layer/empty.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// DigestSHA256EmptyTar is the canonical sha256 digest of empty tar file -
|
||||
// (1024 NULL bytes)
|
||||
const DigestSHA256EmptyTar = DiffID("sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef")
|
||||
|
||||
type emptyLayer struct{}
|
||||
|
||||
// EmptyLayer is a layer that corresponds to empty tar.
|
||||
var EmptyLayer = &emptyLayer{}
|
||||
|
||||
func (el *emptyLayer) TarStream() (io.ReadCloser, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
tarWriter := tar.NewWriter(buf)
|
||||
tarWriter.Close()
|
||||
return ioutil.NopCloser(buf), nil
|
||||
}
|
||||
|
||||
func (el *emptyLayer) TarStreamFrom(p ChainID) (io.ReadCloser, error) {
|
||||
if p == "" {
|
||||
return el.TarStream()
|
||||
}
|
||||
return nil, fmt.Errorf("can't get parent tar stream of an empty layer")
|
||||
}
|
||||
|
||||
func (el *emptyLayer) ChainID() ChainID {
|
||||
return ChainID(DigestSHA256EmptyTar)
|
||||
}
|
||||
|
||||
func (el *emptyLayer) DiffID() DiffID {
|
||||
return DigestSHA256EmptyTar
|
||||
}
|
||||
|
||||
func (el *emptyLayer) Parent() Layer {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (el *emptyLayer) Size() (size int64, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (el *emptyLayer) DiffSize() (size int64, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (el *emptyLayer) Metadata() (map[string]string, error) {
|
||||
return make(map[string]string), nil
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the layer is an EmptyLayer
|
||||
func IsEmpty(diffID DiffID) bool {
|
||||
return diffID == DigestSHA256EmptyTar
|
||||
}
|
52
vendor/github.com/docker/docker-ce/components/engine/layer/empty_test.go
generated
vendored
Normal file
52
vendor/github.com/docker/docker-ce/components/engine/layer/empty_test.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func TestEmptyLayer(t *testing.T) {
|
||||
if EmptyLayer.ChainID() != ChainID(DigestSHA256EmptyTar) {
|
||||
t.Fatal("wrong ChainID for empty layer")
|
||||
}
|
||||
|
||||
if EmptyLayer.DiffID() != DigestSHA256EmptyTar {
|
||||
t.Fatal("wrong DiffID for empty layer")
|
||||
}
|
||||
|
||||
if EmptyLayer.Parent() != nil {
|
||||
t.Fatal("expected no parent for empty layer")
|
||||
}
|
||||
|
||||
if size, err := EmptyLayer.Size(); err != nil || size != 0 {
|
||||
t.Fatal("expected zero size for empty layer")
|
||||
}
|
||||
|
||||
if diffSize, err := EmptyLayer.DiffSize(); err != nil || diffSize != 0 {
|
||||
t.Fatal("expected zero diffsize for empty layer")
|
||||
}
|
||||
|
||||
meta, err := EmptyLayer.Metadata()
|
||||
|
||||
if len(meta) != 0 || err != nil {
|
||||
t.Fatal("expected zero length metadata for empty layer")
|
||||
}
|
||||
|
||||
tarStream, err := EmptyLayer.TarStream()
|
||||
if err != nil {
|
||||
t.Fatalf("error streaming tar for empty layer: %v", err)
|
||||
}
|
||||
|
||||
digester := digest.Canonical.Digester()
|
||||
_, err = io.Copy(digester.Hash(), tarStream)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error hashing empty tar layer: %v", err)
|
||||
}
|
||||
|
||||
if digester.Digest() != digest.Digest(DigestSHA256EmptyTar) {
|
||||
t.Fatal("empty layer tar stream hashes to wrong value")
|
||||
}
|
||||
}
|
355
vendor/github.com/docker/docker-ce/components/engine/layer/filestore.go
generated
vendored
Normal file
355
vendor/github.com/docker/docker-ce/components/engine/layer/filestore.go
generated
vendored
Normal file
|
@ -0,0 +1,355 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
stringIDRegexp = regexp.MustCompile(`^[a-f0-9]{64}(-init)?$`)
|
||||
supportedAlgorithms = []digest.Algorithm{
|
||||
digest.SHA256,
|
||||
// digest.SHA384, // Currently not used
|
||||
// digest.SHA512, // Currently not used
|
||||
}
|
||||
)
|
||||
|
||||
type fileMetadataStore struct {
|
||||
root string
|
||||
}
|
||||
|
||||
type fileMetadataTransaction struct {
|
||||
store *fileMetadataStore
|
||||
ws *ioutils.AtomicWriteSet
|
||||
}
|
||||
|
||||
// newFSMetadataStore returns an instance of a metadata store
|
||||
// which is backed by files on disk using the provided root
|
||||
// as the root of metadata files.
|
||||
func newFSMetadataStore(root string) (*fileMetadataStore, error) {
|
||||
if err := os.MkdirAll(root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fileMetadataStore{
|
||||
root: root,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) getLayerDirectory(layer ChainID) string {
|
||||
dgst := digest.Digest(layer)
|
||||
return filepath.Join(fms.root, string(dgst.Algorithm()), dgst.Hex())
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) getLayerFilename(layer ChainID, filename string) string {
|
||||
return filepath.Join(fms.getLayerDirectory(layer), filename)
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) getMountDirectory(mount string) string {
|
||||
return filepath.Join(fms.root, "mounts", mount)
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) getMountFilename(mount, filename string) string {
|
||||
return filepath.Join(fms.getMountDirectory(mount), filename)
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) StartTransaction() (*fileMetadataTransaction, error) {
|
||||
tmpDir := filepath.Join(fms.root, "tmp")
|
||||
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ws, err := ioutils.NewAtomicWriteSet(tmpDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &fileMetadataTransaction{
|
||||
store: fms,
|
||||
ws: ws,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) SetSize(size int64) error {
|
||||
content := fmt.Sprintf("%d", size)
|
||||
return fm.ws.WriteFile("size", []byte(content), 0644)
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) SetParent(parent ChainID) error {
|
||||
return fm.ws.WriteFile("parent", []byte(digest.Digest(parent).String()), 0644)
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) SetDiffID(diff DiffID) error {
|
||||
return fm.ws.WriteFile("diff", []byte(digest.Digest(diff).String()), 0644)
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) SetCacheID(cacheID string) error {
|
||||
return fm.ws.WriteFile("cache-id", []byte(cacheID), 0644)
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) SetDescriptor(ref distribution.Descriptor) error {
|
||||
jsonRef, err := json.Marshal(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fm.ws.WriteFile("descriptor.json", jsonRef, 0644)
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) TarSplitWriter(compressInput bool) (io.WriteCloser, error) {
|
||||
f, err := fm.ws.FileWriter("tar-split.json.gz", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var wc io.WriteCloser
|
||||
if compressInput {
|
||||
wc = gzip.NewWriter(f)
|
||||
} else {
|
||||
wc = f
|
||||
}
|
||||
|
||||
return ioutils.NewWriteCloserWrapper(wc, func() error {
|
||||
wc.Close()
|
||||
return f.Close()
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) Commit(layer ChainID) error {
|
||||
finalDir := fm.store.getLayerDirectory(layer)
|
||||
if err := os.MkdirAll(filepath.Dir(finalDir), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fm.ws.Commit(finalDir)
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) Cancel() error {
|
||||
return fm.ws.Cancel()
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) String() string {
|
||||
return fm.ws.String()
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetSize(layer ChainID) (int64, error) {
|
||||
content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "size"))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
size, err := strconv.ParseInt(string(content), 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetParent(layer ChainID) (ChainID, error) {
|
||||
content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "parent"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
dgst, err := digest.Parse(strings.TrimSpace(string(content)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ChainID(dgst), nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetDiffID(layer ChainID) (DiffID, error) {
|
||||
content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "diff"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dgst, err := digest.Parse(strings.TrimSpace(string(content)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return DiffID(dgst), nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetCacheID(layer ChainID) (string, error) {
|
||||
contentBytes, err := ioutil.ReadFile(fms.getLayerFilename(layer, "cache-id"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
content := strings.TrimSpace(string(contentBytes))
|
||||
|
||||
if !stringIDRegexp.MatchString(content) {
|
||||
return "", errors.New("invalid cache id value")
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetDescriptor(layer ChainID) (distribution.Descriptor, error) {
|
||||
content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "descriptor.json"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// only return empty descriptor to represent what is stored
|
||||
return distribution.Descriptor{}, nil
|
||||
}
|
||||
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) {
|
||||
fz, err := os.Open(fms.getLayerFilename(layer, "tar-split.json.gz"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := gzip.NewReader(fz)
|
||||
if err != nil {
|
||||
fz.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ioutils.NewReadCloserWrapper(f, func() error {
|
||||
f.Close()
|
||||
return fz.Close()
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) SetMountID(mount string, mountID string) error {
|
||||
if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(fms.getMountFilename(mount, "mount-id"), []byte(mountID), 0644)
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) SetInitID(mount string, init string) error {
|
||||
if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(fms.getMountFilename(mount, "init-id"), []byte(init), 0644)
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) SetMountParent(mount string, parent ChainID) error {
|
||||
if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(fms.getMountFilename(mount, "parent"), []byte(digest.Digest(parent).String()), 0644)
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetMountID(mount string) (string, error) {
|
||||
contentBytes, err := ioutil.ReadFile(fms.getMountFilename(mount, "mount-id"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
content := strings.TrimSpace(string(contentBytes))
|
||||
|
||||
if !stringIDRegexp.MatchString(content) {
|
||||
return "", errors.New("invalid mount id value")
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetInitID(mount string) (string, error) {
|
||||
contentBytes, err := ioutil.ReadFile(fms.getMountFilename(mount, "init-id"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
content := strings.TrimSpace(string(contentBytes))
|
||||
|
||||
if !stringIDRegexp.MatchString(content) {
|
||||
return "", errors.New("invalid init id value")
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetMountParent(mount string) (ChainID, error) {
|
||||
content, err := ioutil.ReadFile(fms.getMountFilename(mount, "parent"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
dgst, err := digest.Parse(strings.TrimSpace(string(content)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ChainID(dgst), nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) List() ([]ChainID, []string, error) {
|
||||
var ids []ChainID
|
||||
for _, algorithm := range supportedAlgorithms {
|
||||
fileInfos, err := ioutil.ReadDir(filepath.Join(fms.root, string(algorithm)))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, fi := range fileInfos {
|
||||
if fi.IsDir() && fi.Name() != "mounts" {
|
||||
dgst := digest.NewDigestFromHex(string(algorithm), fi.Name())
|
||||
if err := dgst.Validate(); err != nil {
|
||||
logrus.Debugf("Ignoring invalid digest %s:%s", algorithm, fi.Name())
|
||||
} else {
|
||||
ids = append(ids, ChainID(dgst))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileInfos, err := ioutil.ReadDir(filepath.Join(fms.root, "mounts"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return ids, []string{}, nil
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var mounts []string
|
||||
for _, fi := range fileInfos {
|
||||
if fi.IsDir() {
|
||||
mounts = append(mounts, fi.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return ids, mounts, nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) Remove(layer ChainID) error {
|
||||
return os.RemoveAll(fms.getLayerDirectory(layer))
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) RemoveMount(mount string) error {
|
||||
return os.RemoveAll(fms.getMountDirectory(mount))
|
||||
}
|
104
vendor/github.com/docker/docker-ce/components/engine/layer/filestore_test.go
generated
vendored
Normal file
104
vendor/github.com/docker/docker-ce/components/engine/layer/filestore_test.go
generated
vendored
Normal file
|
@ -0,0 +1,104 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func randomLayerID(seed int64) ChainID {
|
||||
r := rand.New(rand.NewSource(seed))
|
||||
|
||||
return ChainID(digest.FromBytes([]byte(fmt.Sprintf("%d", r.Int63()))))
|
||||
}
|
||||
|
||||
func newFileMetadataStore(t *testing.T) (*fileMetadataStore, string, func()) {
|
||||
td, err := ioutil.TempDir("", "layers-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fms, err := newFSMetadataStore(td)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return fms, td, func() {
|
||||
if err := os.RemoveAll(td); err != nil {
|
||||
t.Logf("Failed to cleanup %q: %s", td, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertNotDirectoryError(t *testing.T, err error) {
|
||||
perr, ok := err.(*os.PathError)
|
||||
if !ok {
|
||||
t.Fatalf("Unexpected error %#v, expected path error", err)
|
||||
}
|
||||
|
||||
if perr.Err != syscall.ENOTDIR {
|
||||
t.Fatalf("Unexpected error %s, expected %s", perr.Err, syscall.ENOTDIR)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitFailure(t *testing.T) {
|
||||
fms, td, cleanup := newFileMetadataStore(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(td, "sha256"), []byte("was here first!"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tx, err := fms.StartTransaction()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := tx.SetSize(0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = tx.Commit(randomLayerID(5))
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error committing with invalid layer parent directory")
|
||||
}
|
||||
assertNotDirectoryError(t, err)
|
||||
}
|
||||
|
||||
func TestStartTransactionFailure(t *testing.T) {
|
||||
fms, td, cleanup := newFileMetadataStore(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(td, "tmp"), []byte("was here first!"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := fms.StartTransaction()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error starting transaction with invalid layer parent directory")
|
||||
}
|
||||
assertNotDirectoryError(t, err)
|
||||
|
||||
if err := os.Remove(filepath.Join(td, "tmp")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tx, err := fms.StartTransaction()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := filepath.Join(td, "tmp"); strings.HasPrefix(expected, tx.String()) {
|
||||
t.Fatalf("Unexpected transaction string %q, expected prefix %q", tx.String(), expected)
|
||||
}
|
||||
|
||||
if err := tx.Cancel(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
15
vendor/github.com/docker/docker-ce/components/engine/layer/filestore_unix.go
generated
vendored
Normal file
15
vendor/github.com/docker/docker-ce/components/engine/layer/filestore_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
// +build !windows
|
||||
|
||||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import "runtime"
|
||||
|
||||
// setOS writes the "os" file to the layer filestore
|
||||
func (fm *fileMetadataTransaction) setOS(os string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getOS reads the "os" file from the layer filestore
|
||||
func (fms *fileMetadataStore) getOS(layer ChainID) (string, error) {
|
||||
return runtime.GOOS, nil
|
||||
}
|
35
vendor/github.com/docker/docker-ce/components/engine/layer/filestore_windows.go
generated
vendored
Normal file
35
vendor/github.com/docker/docker-ce/components/engine/layer/filestore_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// setOS writes the "os" file to the layer filestore
|
||||
func (fm *fileMetadataTransaction) setOS(os string) error {
|
||||
if os == "" {
|
||||
return nil
|
||||
}
|
||||
return fm.ws.WriteFile("os", []byte(os), 0644)
|
||||
}
|
||||
|
||||
// getOS reads the "os" file from the layer filestore
|
||||
func (fms *fileMetadataStore) getOS(layer ChainID) (string, error) {
|
||||
contentBytes, err := ioutil.ReadFile(fms.getLayerFilename(layer, "os"))
|
||||
if err != nil {
|
||||
// For backwards compatibility, the os file may not exist. Default to "windows" if missing.
|
||||
if os.IsNotExist(err) {
|
||||
return "windows", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
content := strings.TrimSpace(string(contentBytes))
|
||||
|
||||
if content != "windows" && content != "linux" {
|
||||
return "", fmt.Errorf("invalid operating system value: %s", content)
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
237
vendor/github.com/docker/docker-ce/components/engine/layer/layer.go
generated
vendored
Normal file
237
vendor/github.com/docker/docker-ce/components/engine/layer/layer.go
generated
vendored
Normal file
|
@ -0,0 +1,237 @@
|
|||
// Package layer is package for managing read-only
|
||||
// and read-write mounts on the union file system
|
||||
// driver. Read-only mounts are referenced using a
|
||||
// content hash and are protected from mutation in
|
||||
// the exposed interface. The tar format is used
|
||||
// to create read-only layers and export both
|
||||
// read-only and writable layers. The exported
|
||||
// tar data for a read-only layer should match
|
||||
// the tar used to create the layer.
|
||||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/containerfs"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrLayerDoesNotExist is used when an operation is
|
||||
// attempted on a layer which does not exist.
|
||||
ErrLayerDoesNotExist = errors.New("layer does not exist")
|
||||
|
||||
// ErrLayerNotRetained is used when a release is
|
||||
// attempted on a layer which is not retained.
|
||||
ErrLayerNotRetained = errors.New("layer not retained")
|
||||
|
||||
// ErrMountDoesNotExist is used when an operation is
|
||||
// attempted on a mount layer which does not exist.
|
||||
ErrMountDoesNotExist = errors.New("mount does not exist")
|
||||
|
||||
// ErrMountNameConflict is used when a mount is attempted
|
||||
// to be created but there is already a mount with the name
|
||||
// used for creation.
|
||||
ErrMountNameConflict = errors.New("mount already exists with name")
|
||||
|
||||
// ErrActiveMount is used when an operation on a
|
||||
// mount is attempted but the layer is still
|
||||
// mounted and the operation cannot be performed.
|
||||
ErrActiveMount = errors.New("mount still active")
|
||||
|
||||
// ErrNotMounted is used when requesting an active
|
||||
// mount but the layer is not mounted.
|
||||
ErrNotMounted = errors.New("not mounted")
|
||||
|
||||
// ErrMaxDepthExceeded is used when a layer is attempted
|
||||
// to be created which would result in a layer depth
|
||||
// greater than the 125 max.
|
||||
ErrMaxDepthExceeded = errors.New("max depth exceeded")
|
||||
|
||||
// ErrNotSupported is used when the action is not supported
|
||||
// on the current host operating system.
|
||||
ErrNotSupported = errors.New("not support on this host operating system")
|
||||
)
|
||||
|
||||
// ChainID is the content-addressable ID of a layer.
|
||||
type ChainID digest.Digest
|
||||
|
||||
// String returns a string rendition of a layer ID
|
||||
func (id ChainID) String() string {
|
||||
return string(id)
|
||||
}
|
||||
|
||||
// DiffID is the hash of an individual layer tar.
|
||||
type DiffID digest.Digest
|
||||
|
||||
// String returns a string rendition of a layer DiffID
|
||||
func (diffID DiffID) String() string {
|
||||
return string(diffID)
|
||||
}
|
||||
|
||||
// TarStreamer represents an object which may
|
||||
// have its contents exported as a tar stream.
|
||||
type TarStreamer interface {
|
||||
// TarStream returns a tar archive stream
|
||||
// for the contents of a layer.
|
||||
TarStream() (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// Layer represents a read-only layer
|
||||
type Layer interface {
|
||||
TarStreamer
|
||||
|
||||
// TarStreamFrom returns a tar archive stream for all the layer chain with
|
||||
// arbitrary depth.
|
||||
TarStreamFrom(ChainID) (io.ReadCloser, error)
|
||||
|
||||
// ChainID returns the content hash of the entire layer chain. The hash
|
||||
// chain is made up of DiffID of top layer and all of its parents.
|
||||
ChainID() ChainID
|
||||
|
||||
// DiffID returns the content hash of the layer
|
||||
// tar stream used to create this layer.
|
||||
DiffID() DiffID
|
||||
|
||||
// Parent returns the next layer in the layer chain.
|
||||
Parent() Layer
|
||||
|
||||
// Size returns the size of the entire layer chain. The size
|
||||
// is calculated from the total size of all files in the layers.
|
||||
Size() (int64, error)
|
||||
|
||||
// DiffSize returns the size difference of the top layer
|
||||
// from parent layer.
|
||||
DiffSize() (int64, error)
|
||||
|
||||
// Metadata returns the low level storage metadata associated
|
||||
// with layer.
|
||||
Metadata() (map[string]string, error)
|
||||
}
|
||||
|
||||
// RWLayer represents a layer which is
|
||||
// read and writable
|
||||
type RWLayer interface {
|
||||
TarStreamer
|
||||
|
||||
// Name of mounted layer
|
||||
Name() string
|
||||
|
||||
// Parent returns the layer which the writable
|
||||
// layer was created from.
|
||||
Parent() Layer
|
||||
|
||||
// Mount mounts the RWLayer and returns the filesystem path
|
||||
// the to the writable layer.
|
||||
Mount(mountLabel string) (containerfs.ContainerFS, error)
|
||||
|
||||
// Unmount unmounts the RWLayer. This should be called
|
||||
// for every mount. If there are multiple mount calls
|
||||
// this operation will only decrement the internal mount counter.
|
||||
Unmount() error
|
||||
|
||||
// Size represents the size of the writable layer
|
||||
// as calculated by the total size of the files
|
||||
// changed in the mutable layer.
|
||||
Size() (int64, error)
|
||||
|
||||
// Changes returns the set of changes for the mutable layer
|
||||
// from the base layer.
|
||||
Changes() ([]archive.Change, error)
|
||||
|
||||
// Metadata returns the low level metadata for the mutable layer
|
||||
Metadata() (map[string]string, error)
|
||||
}
|
||||
|
||||
// Metadata holds information about a
|
||||
// read-only layer
|
||||
type Metadata struct {
|
||||
// ChainID is the content hash of the layer
|
||||
ChainID ChainID
|
||||
|
||||
// DiffID is the hash of the tar data used to
|
||||
// create the layer
|
||||
DiffID DiffID
|
||||
|
||||
// Size is the size of the layer and all parents
|
||||
Size int64
|
||||
|
||||
// DiffSize is the size of the top layer
|
||||
DiffSize int64
|
||||
}
|
||||
|
||||
// MountInit is a function to initialize a
|
||||
// writable mount. Changes made here will
|
||||
// not be included in the Tar stream of the
|
||||
// RWLayer.
|
||||
type MountInit func(root containerfs.ContainerFS) error
|
||||
|
||||
// CreateRWLayerOpts contains optional arguments to be passed to CreateRWLayer
|
||||
type CreateRWLayerOpts struct {
|
||||
MountLabel string
|
||||
InitFunc MountInit
|
||||
StorageOpt map[string]string
|
||||
}
|
||||
|
||||
// Store represents a backend for managing both
|
||||
// read-only and read-write layers.
|
||||
type Store interface {
|
||||
Register(io.Reader, ChainID) (Layer, error)
|
||||
Get(ChainID) (Layer, error)
|
||||
Map() map[ChainID]Layer
|
||||
Release(Layer) ([]Metadata, error)
|
||||
|
||||
CreateRWLayer(id string, parent ChainID, opts *CreateRWLayerOpts) (RWLayer, error)
|
||||
GetRWLayer(id string) (RWLayer, error)
|
||||
GetMountID(id string) (string, error)
|
||||
ReleaseRWLayer(RWLayer) ([]Metadata, error)
|
||||
|
||||
Cleanup() error
|
||||
DriverStatus() [][2]string
|
||||
DriverName() string
|
||||
}
|
||||
|
||||
// DescribableStore represents a layer store capable of storing
|
||||
// descriptors for layers.
|
||||
type DescribableStore interface {
|
||||
RegisterWithDescriptor(io.Reader, ChainID, distribution.Descriptor) (Layer, error)
|
||||
}
|
||||
|
||||
// CreateChainID returns ID for a layerDigest slice
|
||||
func CreateChainID(dgsts []DiffID) ChainID {
|
||||
return createChainIDFromParent("", dgsts...)
|
||||
}
|
||||
|
||||
func createChainIDFromParent(parent ChainID, dgsts ...DiffID) ChainID {
|
||||
if len(dgsts) == 0 {
|
||||
return parent
|
||||
}
|
||||
if parent == "" {
|
||||
return createChainIDFromParent(ChainID(dgsts[0]), dgsts[1:]...)
|
||||
}
|
||||
// H = "H(n-1) SHA256(n)"
|
||||
dgst := digest.FromBytes([]byte(string(parent) + " " + string(dgsts[0])))
|
||||
return createChainIDFromParent(ChainID(dgst), dgsts[1:]...)
|
||||
}
|
||||
|
||||
// ReleaseAndLog releases the provided layer from the given layer
|
||||
// store, logging any error and release metadata
|
||||
func ReleaseAndLog(ls Store, l Layer) {
|
||||
metadata, err := ls.Release(l)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error releasing layer %s: %v", l.ChainID(), err)
|
||||
}
|
||||
LogReleaseMetadata(metadata)
|
||||
}
|
||||
|
||||
// LogReleaseMetadata logs a metadata array, uses this to
|
||||
// ensure consistent logging for release metadata
|
||||
func LogReleaseMetadata(metadatas []Metadata) {
|
||||
for _, metadata := range metadatas {
|
||||
logrus.Infof("Layer %s cleaned up", metadata.ChainID)
|
||||
}
|
||||
}
|
750
vendor/github.com/docker/docker-ce/components/engine/layer/layer_store.go
generated
vendored
Normal file
750
vendor/github.com/docker/docker-ce/components/engine/layer/layer_store.go
generated
vendored
Normal file
|
@ -0,0 +1,750 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/plugingetter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vbatts/tar-split/tar/asm"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
)
|
||||
|
||||
// maxLayerDepth represents the maximum number of
|
||||
// layers which can be chained together. 125 was
|
||||
// chosen to account for the 127 max in some
|
||||
// graphdrivers plus the 2 additional layers
|
||||
// used to create a rwlayer.
|
||||
const maxLayerDepth = 125
|
||||
|
||||
type layerStore struct {
|
||||
store *fileMetadataStore
|
||||
driver graphdriver.Driver
|
||||
useTarSplit bool
|
||||
|
||||
layerMap map[ChainID]*roLayer
|
||||
layerL sync.Mutex
|
||||
|
||||
mounts map[string]*mountedLayer
|
||||
mountL sync.Mutex
|
||||
os string
|
||||
}
|
||||
|
||||
// StoreOptions are the options used to create a new Store instance
|
||||
type StoreOptions struct {
|
||||
Root string
|
||||
MetadataStorePathTemplate string
|
||||
GraphDriver string
|
||||
GraphDriverOptions []string
|
||||
IDMappings *idtools.IDMappings
|
||||
PluginGetter plugingetter.PluginGetter
|
||||
ExperimentalEnabled bool
|
||||
OS string
|
||||
}
|
||||
|
||||
// NewStoreFromOptions creates a new Store instance
|
||||
func NewStoreFromOptions(options StoreOptions) (Store, error) {
|
||||
driver, err := graphdriver.New(options.GraphDriver, options.PluginGetter, graphdriver.Options{
|
||||
Root: options.Root,
|
||||
DriverOptions: options.GraphDriverOptions,
|
||||
UIDMaps: options.IDMappings.UIDs(),
|
||||
GIDMaps: options.IDMappings.GIDs(),
|
||||
ExperimentalEnabled: options.ExperimentalEnabled,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error initializing graphdriver: %v", err)
|
||||
}
|
||||
logrus.Debugf("Initialized graph driver %s", driver)
|
||||
|
||||
root := fmt.Sprintf(options.MetadataStorePathTemplate, driver)
|
||||
|
||||
return newStoreFromGraphDriver(root, driver, options.OS)
|
||||
}
|
||||
|
||||
// newStoreFromGraphDriver creates a new Store instance using the provided
|
||||
// metadata store and graph driver. The metadata store will be used to restore
|
||||
// the Store.
|
||||
func newStoreFromGraphDriver(root string, driver graphdriver.Driver, os string) (Store, error) {
|
||||
if !system.IsOSSupported(os) {
|
||||
return nil, fmt.Errorf("failed to initialize layer store as operating system '%s' is not supported", os)
|
||||
}
|
||||
caps := graphdriver.Capabilities{}
|
||||
if capDriver, ok := driver.(graphdriver.CapabilityDriver); ok {
|
||||
caps = capDriver.Capabilities()
|
||||
}
|
||||
|
||||
ms, err := newFSMetadataStore(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ls := &layerStore{
|
||||
store: ms,
|
||||
driver: driver,
|
||||
layerMap: map[ChainID]*roLayer{},
|
||||
mounts: map[string]*mountedLayer{},
|
||||
useTarSplit: !caps.ReproducesExactDiffs,
|
||||
os: os,
|
||||
}
|
||||
|
||||
ids, mounts, err := ms.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
l, err := ls.loadLayer(id)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to load layer %s: %s", id, err)
|
||||
continue
|
||||
}
|
||||
if l.parent != nil {
|
||||
l.parent.referenceCount++
|
||||
}
|
||||
}
|
||||
|
||||
for _, mount := range mounts {
|
||||
if err := ls.loadMount(mount); err != nil {
|
||||
logrus.Debugf("Failed to load mount %s: %s", mount, err)
|
||||
}
|
||||
}
|
||||
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) {
|
||||
cl, ok := ls.layerMap[layer]
|
||||
if ok {
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
diff, err := ls.store.GetDiffID(layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get diff id for %s: %s", layer, err)
|
||||
}
|
||||
|
||||
size, err := ls.store.GetSize(layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get size for %s: %s", layer, err)
|
||||
}
|
||||
|
||||
cacheID, err := ls.store.GetCacheID(layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get cache id for %s: %s", layer, err)
|
||||
}
|
||||
|
||||
parent, err := ls.store.GetParent(layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get parent for %s: %s", layer, err)
|
||||
}
|
||||
|
||||
descriptor, err := ls.store.GetDescriptor(layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get descriptor for %s: %s", layer, err)
|
||||
}
|
||||
|
||||
os, err := ls.store.getOS(layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get operating system for %s: %s", layer, err)
|
||||
}
|
||||
|
||||
if os != ls.os {
|
||||
return nil, fmt.Errorf("failed to load layer with os %s into layerstore for %s", os, ls.os)
|
||||
}
|
||||
|
||||
cl = &roLayer{
|
||||
chainID: layer,
|
||||
diffID: diff,
|
||||
size: size,
|
||||
cacheID: cacheID,
|
||||
layerStore: ls,
|
||||
references: map[Layer]struct{}{},
|
||||
descriptor: descriptor,
|
||||
}
|
||||
|
||||
if parent != "" {
|
||||
p, err := ls.loadLayer(parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cl.parent = p
|
||||
}
|
||||
|
||||
ls.layerMap[cl.chainID] = cl
|
||||
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) loadMount(mount string) error {
|
||||
if _, ok := ls.mounts[mount]; ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
mountID, err := ls.store.GetMountID(mount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
initID, err := ls.store.GetInitID(mount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parent, err := ls.store.GetMountParent(mount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ml := &mountedLayer{
|
||||
name: mount,
|
||||
mountID: mountID,
|
||||
initID: initID,
|
||||
layerStore: ls,
|
||||
references: map[RWLayer]*referencedRWLayer{},
|
||||
}
|
||||
|
||||
if parent != "" {
|
||||
p, err := ls.loadLayer(parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ml.parent = p
|
||||
|
||||
p.referenceCount++
|
||||
}
|
||||
|
||||
ls.mounts[ml.name] = ml
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) applyTar(tx *fileMetadataTransaction, ts io.Reader, parent string, layer *roLayer) error {
|
||||
digester := digest.Canonical.Digester()
|
||||
tr := io.TeeReader(ts, digester.Hash())
|
||||
|
||||
rdr := tr
|
||||
if ls.useTarSplit {
|
||||
tsw, err := tx.TarSplitWriter(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metaPacker := storage.NewJSONPacker(tsw)
|
||||
defer tsw.Close()
|
||||
|
||||
// we're passing nil here for the file putter, because the ApplyDiff will
|
||||
// handle the extraction of the archive
|
||||
rdr, err = asm.NewInputTarStream(tr, metaPacker, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
applySize, err := ls.driver.ApplyDiff(layer.cacheID, parent, rdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Discard trailing data but ensure metadata is picked up to reconstruct stream
|
||||
io.Copy(ioutil.Discard, rdr) // ignore error as reader may be closed
|
||||
|
||||
layer.size = applySize
|
||||
layer.diffID = DiffID(digester.Digest())
|
||||
|
||||
logrus.Debugf("Applied tar %s to %s, size: %d", layer.diffID, layer.cacheID, applySize)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) Register(ts io.Reader, parent ChainID) (Layer, error) {
|
||||
return ls.registerWithDescriptor(ts, parent, distribution.Descriptor{})
|
||||
}
|
||||
|
||||
func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descriptor distribution.Descriptor) (Layer, error) {
|
||||
// err is used to hold the error which will always trigger
|
||||
// cleanup of creates sources but may not be an error returned
|
||||
// to the caller (already exists).
|
||||
var err error
|
||||
var pid string
|
||||
var p *roLayer
|
||||
|
||||
if string(parent) != "" {
|
||||
p = ls.get(parent)
|
||||
if p == nil {
|
||||
return nil, ErrLayerDoesNotExist
|
||||
}
|
||||
pid = p.cacheID
|
||||
// Release parent chain if error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ls.layerL.Lock()
|
||||
ls.releaseLayer(p)
|
||||
ls.layerL.Unlock()
|
||||
}
|
||||
}()
|
||||
if p.depth() >= maxLayerDepth {
|
||||
err = ErrMaxDepthExceeded
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Create new roLayer
|
||||
layer := &roLayer{
|
||||
parent: p,
|
||||
cacheID: stringid.GenerateRandomID(),
|
||||
referenceCount: 1,
|
||||
layerStore: ls,
|
||||
references: map[Layer]struct{}{},
|
||||
descriptor: descriptor,
|
||||
}
|
||||
|
||||
if err = ls.driver.Create(layer.cacheID, pid, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx, err := ls.store.StartTransaction()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logrus.Debugf("Cleaning up layer %s: %v", layer.cacheID, err)
|
||||
if err := ls.driver.Remove(layer.cacheID); err != nil {
|
||||
logrus.Errorf("Error cleaning up cache layer %s: %v", layer.cacheID, err)
|
||||
}
|
||||
if err := tx.Cancel(); err != nil {
|
||||
logrus.Errorf("Error canceling metadata transaction %q: %s", tx.String(), err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err = ls.applyTar(tx, ts, pid, layer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if layer.parent == nil {
|
||||
layer.chainID = ChainID(layer.diffID)
|
||||
} else {
|
||||
layer.chainID = createChainIDFromParent(layer.parent.chainID, layer.diffID)
|
||||
}
|
||||
|
||||
if err = storeLayer(tx, layer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
|
||||
if existingLayer := ls.getWithoutLock(layer.chainID); existingLayer != nil {
|
||||
// Set error for cleanup, but do not return the error
|
||||
err = errors.New("layer already exists")
|
||||
return existingLayer.getReference(), nil
|
||||
}
|
||||
|
||||
if err = tx.Commit(layer.chainID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ls.layerMap[layer.chainID] = layer
|
||||
|
||||
return layer.getReference(), nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) getWithoutLock(layer ChainID) *roLayer {
|
||||
l, ok := ls.layerMap[layer]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.referenceCount++
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func (ls *layerStore) get(l ChainID) *roLayer {
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
return ls.getWithoutLock(l)
|
||||
}
|
||||
|
||||
func (ls *layerStore) Get(l ChainID) (Layer, error) {
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
|
||||
layer := ls.getWithoutLock(l)
|
||||
if layer == nil {
|
||||
return nil, ErrLayerDoesNotExist
|
||||
}
|
||||
|
||||
return layer.getReference(), nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) Map() map[ChainID]Layer {
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
|
||||
layers := map[ChainID]Layer{}
|
||||
|
||||
for k, v := range ls.layerMap {
|
||||
layers[k] = v
|
||||
}
|
||||
|
||||
return layers
|
||||
}
|
||||
|
||||
func (ls *layerStore) deleteLayer(layer *roLayer, metadata *Metadata) error {
|
||||
err := ls.driver.Remove(layer.cacheID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ls.store.Remove(layer.chainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metadata.DiffID = layer.diffID
|
||||
metadata.ChainID = layer.chainID
|
||||
metadata.Size, err = layer.Size()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metadata.DiffSize = layer.size
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) releaseLayer(l *roLayer) ([]Metadata, error) {
|
||||
depth := 0
|
||||
removed := []Metadata{}
|
||||
for {
|
||||
if l.referenceCount == 0 {
|
||||
panic("layer not retained")
|
||||
}
|
||||
l.referenceCount--
|
||||
if l.referenceCount != 0 {
|
||||
return removed, nil
|
||||
}
|
||||
|
||||
if len(removed) == 0 && depth > 0 {
|
||||
panic("cannot remove layer with child")
|
||||
}
|
||||
if l.hasReferences() {
|
||||
panic("cannot delete referenced layer")
|
||||
}
|
||||
var metadata Metadata
|
||||
if err := ls.deleteLayer(l, &metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(ls.layerMap, l.chainID)
|
||||
removed = append(removed, metadata)
|
||||
|
||||
if l.parent == nil {
|
||||
return removed, nil
|
||||
}
|
||||
|
||||
depth++
|
||||
l = l.parent
|
||||
}
|
||||
}
|
||||
|
||||
func (ls *layerStore) Release(l Layer) ([]Metadata, error) {
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
layer, ok := ls.layerMap[l.ChainID()]
|
||||
if !ok {
|
||||
return []Metadata{}, nil
|
||||
}
|
||||
if !layer.hasReference(l) {
|
||||
return nil, ErrLayerNotRetained
|
||||
}
|
||||
|
||||
layer.deleteReference(l)
|
||||
|
||||
return ls.releaseLayer(layer)
|
||||
}
|
||||
|
||||
func (ls *layerStore) CreateRWLayer(name string, parent ChainID, opts *CreateRWLayerOpts) (RWLayer, error) {
|
||||
var (
|
||||
storageOpt map[string]string
|
||||
initFunc MountInit
|
||||
mountLabel string
|
||||
)
|
||||
|
||||
if opts != nil {
|
||||
mountLabel = opts.MountLabel
|
||||
storageOpt = opts.StorageOpt
|
||||
initFunc = opts.InitFunc
|
||||
}
|
||||
|
||||
ls.mountL.Lock()
|
||||
defer ls.mountL.Unlock()
|
||||
m, ok := ls.mounts[name]
|
||||
if ok {
|
||||
return nil, ErrMountNameConflict
|
||||
}
|
||||
|
||||
var err error
|
||||
var pid string
|
||||
var p *roLayer
|
||||
if string(parent) != "" {
|
||||
p = ls.get(parent)
|
||||
if p == nil {
|
||||
return nil, ErrLayerDoesNotExist
|
||||
}
|
||||
pid = p.cacheID
|
||||
|
||||
// Release parent chain if error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ls.layerL.Lock()
|
||||
ls.releaseLayer(p)
|
||||
ls.layerL.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
m = &mountedLayer{
|
||||
name: name,
|
||||
parent: p,
|
||||
mountID: ls.mountID(name),
|
||||
layerStore: ls,
|
||||
references: map[RWLayer]*referencedRWLayer{},
|
||||
}
|
||||
|
||||
if initFunc != nil {
|
||||
pid, err = ls.initMount(m.mountID, pid, mountLabel, initFunc, storageOpt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.initID = pid
|
||||
}
|
||||
|
||||
createOpts := &graphdriver.CreateOpts{
|
||||
StorageOpt: storageOpt,
|
||||
}
|
||||
|
||||
if err = ls.driver.CreateReadWrite(m.mountID, pid, createOpts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = ls.saveMount(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.getReference(), nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) GetRWLayer(id string) (RWLayer, error) {
|
||||
ls.mountL.Lock()
|
||||
defer ls.mountL.Unlock()
|
||||
mount, ok := ls.mounts[id]
|
||||
if !ok {
|
||||
return nil, ErrMountDoesNotExist
|
||||
}
|
||||
|
||||
return mount.getReference(), nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) GetMountID(id string) (string, error) {
|
||||
ls.mountL.Lock()
|
||||
defer ls.mountL.Unlock()
|
||||
mount, ok := ls.mounts[id]
|
||||
if !ok {
|
||||
return "", ErrMountDoesNotExist
|
||||
}
|
||||
logrus.Debugf("GetMountID id: %s -> mountID: %s", id, mount.mountID)
|
||||
|
||||
return mount.mountID, nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) ReleaseRWLayer(l RWLayer) ([]Metadata, error) {
|
||||
ls.mountL.Lock()
|
||||
defer ls.mountL.Unlock()
|
||||
m, ok := ls.mounts[l.Name()]
|
||||
if !ok {
|
||||
return []Metadata{}, nil
|
||||
}
|
||||
|
||||
if err := m.deleteReference(l); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m.hasReferences() {
|
||||
return []Metadata{}, nil
|
||||
}
|
||||
|
||||
if err := ls.driver.Remove(m.mountID); err != nil {
|
||||
logrus.Errorf("Error removing mounted layer %s: %s", m.name, err)
|
||||
m.retakeReference(l)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m.initID != "" {
|
||||
if err := ls.driver.Remove(m.initID); err != nil {
|
||||
logrus.Errorf("Error removing init layer %s: %s", m.name, err)
|
||||
m.retakeReference(l)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ls.store.RemoveMount(m.name); err != nil {
|
||||
logrus.Errorf("Error removing mount metadata: %s: %s", m.name, err)
|
||||
m.retakeReference(l)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(ls.mounts, m.Name())
|
||||
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
if m.parent != nil {
|
||||
return ls.releaseLayer(m.parent)
|
||||
}
|
||||
|
||||
return []Metadata{}, nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) saveMount(mount *mountedLayer) error {
|
||||
if err := ls.store.SetMountID(mount.name, mount.mountID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if mount.initID != "" {
|
||||
if err := ls.store.SetInitID(mount.name, mount.initID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if mount.parent != nil {
|
||||
if err := ls.store.SetMountParent(mount.name, mount.parent.chainID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ls.mounts[mount.name] = mount
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) initMount(graphID, parent, mountLabel string, initFunc MountInit, storageOpt map[string]string) (string, error) {
|
||||
// Use "<graph-id>-init" to maintain compatibility with graph drivers
|
||||
// which are expecting this layer with this special name. If all
|
||||
// graph drivers can be updated to not rely on knowing about this layer
|
||||
// then the initID should be randomly generated.
|
||||
initID := fmt.Sprintf("%s-init", graphID)
|
||||
|
||||
createOpts := &graphdriver.CreateOpts{
|
||||
MountLabel: mountLabel,
|
||||
StorageOpt: storageOpt,
|
||||
}
|
||||
|
||||
if err := ls.driver.CreateReadWrite(initID, parent, createOpts); err != nil {
|
||||
return "", err
|
||||
}
|
||||
p, err := ls.driver.Get(initID, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := initFunc(p); err != nil {
|
||||
ls.driver.Put(initID)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := ls.driver.Put(initID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return initID, nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) getTarStream(rl *roLayer) (io.ReadCloser, error) {
|
||||
if !ls.useTarSplit {
|
||||
var parentCacheID string
|
||||
if rl.parent != nil {
|
||||
parentCacheID = rl.parent.cacheID
|
||||
}
|
||||
|
||||
return ls.driver.Diff(rl.cacheID, parentCacheID)
|
||||
}
|
||||
|
||||
r, err := ls.store.TarSplitReader(rl.chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
err := ls.assembleTarTo(rl.cacheID, r, nil, pw)
|
||||
if err != nil {
|
||||
pw.CloseWithError(err)
|
||||
} else {
|
||||
pw.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) assembleTarTo(graphID string, metadata io.ReadCloser, size *int64, w io.Writer) error {
|
||||
diffDriver, ok := ls.driver.(graphdriver.DiffGetterDriver)
|
||||
if !ok {
|
||||
diffDriver = &naiveDiffPathDriver{ls.driver}
|
||||
}
|
||||
|
||||
defer metadata.Close()
|
||||
|
||||
// get our relative path to the container
|
||||
fileGetCloser, err := diffDriver.DiffGetter(graphID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileGetCloser.Close()
|
||||
|
||||
metaUnpacker := storage.NewJSONUnpacker(metadata)
|
||||
upackerCounter := &unpackSizeCounter{metaUnpacker, size}
|
||||
logrus.Debugf("Assembling tar data for %s", graphID)
|
||||
return asm.WriteOutputTarStream(fileGetCloser, upackerCounter, w)
|
||||
}
|
||||
|
||||
func (ls *layerStore) Cleanup() error {
|
||||
return ls.driver.Cleanup()
|
||||
}
|
||||
|
||||
func (ls *layerStore) DriverStatus() [][2]string {
|
||||
return ls.driver.Status()
|
||||
}
|
||||
|
||||
func (ls *layerStore) DriverName() string {
|
||||
return ls.driver.String()
|
||||
}
|
||||
|
||||
type naiveDiffPathDriver struct {
|
||||
graphdriver.Driver
|
||||
}
|
||||
|
||||
type fileGetPutter struct {
|
||||
storage.FileGetter
|
||||
driver graphdriver.Driver
|
||||
id string
|
||||
}
|
||||
|
||||
func (w *fileGetPutter) Close() error {
|
||||
return w.driver.Put(w.id)
|
||||
}
|
||||
|
||||
func (n *naiveDiffPathDriver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
|
||||
p, err := n.Driver.Get(id, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fileGetPutter{storage.NewPathFileGetter(p.Path()), n.Driver, id}, nil
|
||||
}
|
11
vendor/github.com/docker/docker-ce/components/engine/layer/layer_store_windows.go
generated
vendored
Normal file
11
vendor/github.com/docker/docker-ce/components/engine/layer/layer_store_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
)
|
||||
|
||||
func (ls *layerStore) RegisterWithDescriptor(ts io.Reader, parent ChainID, descriptor distribution.Descriptor) (Layer, error) {
|
||||
return ls.registerWithDescriptor(ts, parent, descriptor)
|
||||
}
|
768
vendor/github.com/docker/docker-ce/components/engine/layer/layer_test.go
generated
vendored
Normal file
768
vendor/github.com/docker/docker-ce/components/engine/layer/layer_test.go
generated
vendored
Normal file
|
@ -0,0 +1,768 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/continuity/driver"
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
"github.com/docker/docker/daemon/graphdriver/vfs"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/containerfs"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func init() {
|
||||
graphdriver.ApplyUncompressedLayer = archive.UnpackLayer
|
||||
defaultArchiver := archive.NewDefaultArchiver()
|
||||
vfs.CopyDir = defaultArchiver.CopyWithTar
|
||||
}
|
||||
|
||||
func newVFSGraphDriver(td string) (graphdriver.Driver, error) {
|
||||
uidMap := []idtools.IDMap{
|
||||
{
|
||||
ContainerID: 0,
|
||||
HostID: os.Getuid(),
|
||||
Size: 1,
|
||||
},
|
||||
}
|
||||
gidMap := []idtools.IDMap{
|
||||
{
|
||||
ContainerID: 0,
|
||||
HostID: os.Getgid(),
|
||||
Size: 1,
|
||||
},
|
||||
}
|
||||
|
||||
options := graphdriver.Options{Root: td, UIDMaps: uidMap, GIDMaps: gidMap}
|
||||
return graphdriver.GetDriver("vfs", nil, options)
|
||||
}
|
||||
|
||||
func newTestGraphDriver(t *testing.T) (graphdriver.Driver, func()) {
|
||||
td, err := ioutil.TempDir("", "graph-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
driver, err := newVFSGraphDriver(td)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return driver, func() {
|
||||
os.RemoveAll(td)
|
||||
}
|
||||
}
|
||||
|
||||
func newTestStore(t *testing.T) (Store, string, func()) {
|
||||
td, err := ioutil.TempDir("", "layerstore-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
graph, graphcleanup := newTestGraphDriver(t)
|
||||
|
||||
ls, err := newStoreFromGraphDriver(td, graph, runtime.GOOS)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return ls, td, func() {
|
||||
graphcleanup()
|
||||
os.RemoveAll(td)
|
||||
}
|
||||
}
|
||||
|
||||
type layerInit func(root containerfs.ContainerFS) error
|
||||
|
||||
func createLayer(ls Store, parent ChainID, layerFunc layerInit) (Layer, error) {
|
||||
containerID := stringid.GenerateRandomID()
|
||||
mount, err := ls.CreateRWLayer(containerID, parent, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pathFS, err := mount.Mount("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := layerFunc(pathFS); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ts, err := mount.TarStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer ts.Close()
|
||||
|
||||
layer, err := ls.Register(ts, parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mount.Unmount(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := ls.ReleaseRWLayer(mount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return layer, nil
|
||||
}
|
||||
|
||||
type FileApplier interface {
|
||||
ApplyFile(root containerfs.ContainerFS) error
|
||||
}
|
||||
|
||||
type testFile struct {
|
||||
name string
|
||||
content []byte
|
||||
permission os.FileMode
|
||||
}
|
||||
|
||||
func newTestFile(name string, content []byte, perm os.FileMode) FileApplier {
|
||||
return &testFile{
|
||||
name: name,
|
||||
content: content,
|
||||
permission: perm,
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testFile) ApplyFile(root containerfs.ContainerFS) error {
|
||||
fullPath := root.Join(root.Path(), tf.name)
|
||||
if err := root.MkdirAll(root.Dir(fullPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
// Check if already exists
|
||||
if stat, err := root.Stat(fullPath); err == nil && stat.Mode().Perm() != tf.permission {
|
||||
if err := root.Lchmod(fullPath, tf.permission); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return driver.WriteFile(root, fullPath, tf.content, tf.permission)
|
||||
}
|
||||
|
||||
func initWithFiles(files ...FileApplier) layerInit {
|
||||
return func(root containerfs.ContainerFS) error {
|
||||
for _, f := range files {
|
||||
if err := f.ApplyFile(root); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getCachedLayer(l Layer) *roLayer {
|
||||
if rl, ok := l.(*referencedCacheLayer); ok {
|
||||
return rl.roLayer
|
||||
}
|
||||
return l.(*roLayer)
|
||||
}
|
||||
|
||||
func getMountLayer(l RWLayer) *mountedLayer {
|
||||
return l.(*referencedRWLayer).mountedLayer
|
||||
}
|
||||
|
||||
func createMetadata(layers ...Layer) []Metadata {
|
||||
metadata := make([]Metadata, len(layers))
|
||||
for i := range layers {
|
||||
size, err := layers[i].Size()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
metadata[i].ChainID = layers[i].ChainID()
|
||||
metadata[i].DiffID = layers[i].DiffID()
|
||||
metadata[i].Size = size
|
||||
metadata[i].DiffSize = getCachedLayer(layers[i]).size
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
func assertMetadata(t *testing.T, metadata, expectedMetadata []Metadata) {
|
||||
if len(metadata) != len(expectedMetadata) {
|
||||
t.Fatalf("Unexpected number of deletes %d, expected %d", len(metadata), len(expectedMetadata))
|
||||
}
|
||||
|
||||
for i := range metadata {
|
||||
if metadata[i] != expectedMetadata[i] {
|
||||
t.Errorf("Unexpected metadata\n\tExpected: %#v\n\tActual: %#v", expectedMetadata[i], metadata[i])
|
||||
}
|
||||
}
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func releaseAndCheckDeleted(t *testing.T, ls Store, layer Layer, removed ...Layer) {
|
||||
layerCount := len(ls.(*layerStore).layerMap)
|
||||
expectedMetadata := createMetadata(removed...)
|
||||
metadata, err := ls.Release(layer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertMetadata(t, metadata, expectedMetadata)
|
||||
|
||||
if expected := layerCount - len(removed); len(ls.(*layerStore).layerMap) != expected {
|
||||
t.Fatalf("Unexpected number of layers %d, expected %d", len(ls.(*layerStore).layerMap), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func cacheID(l Layer) string {
|
||||
return getCachedLayer(l).cacheID
|
||||
}
|
||||
|
||||
func assertLayerEqual(t *testing.T, l1, l2 Layer) {
|
||||
if l1.ChainID() != l2.ChainID() {
|
||||
t.Fatalf("Mismatched ChainID: %s vs %s", l1.ChainID(), l2.ChainID())
|
||||
}
|
||||
if l1.DiffID() != l2.DiffID() {
|
||||
t.Fatalf("Mismatched DiffID: %s vs %s", l1.DiffID(), l2.DiffID())
|
||||
}
|
||||
|
||||
size1, err := l1.Size()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
size2, err := l2.Size()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if size1 != size2 {
|
||||
t.Fatalf("Mismatched size: %d vs %d", size1, size2)
|
||||
}
|
||||
|
||||
if cacheID(l1) != cacheID(l2) {
|
||||
t.Fatalf("Mismatched cache id: %s vs %s", cacheID(l1), cacheID(l2))
|
||||
}
|
||||
|
||||
p1 := l1.Parent()
|
||||
p2 := l2.Parent()
|
||||
if p1 != nil && p2 != nil {
|
||||
assertLayerEqual(t, p1, p2)
|
||||
} else if p1 != nil || p2 != nil {
|
||||
t.Fatalf("Mismatched parents: %v vs %v", p1, p2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountAndRegister(t *testing.T) {
|
||||
ls, _, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
li := initWithFiles(newTestFile("testfile.txt", []byte("some test data"), 0644))
|
||||
layer, err := createLayer(ls, "", li)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
size, _ := layer.Size()
|
||||
t.Logf("Layer size: %d", size)
|
||||
|
||||
mount2, err := ls.CreateRWLayer("new-test-mount", layer.ChainID(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
path2, err := mount2.Mount("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b, err := driver.ReadFile(path2, path2.Join(path2.Path(), "testfile.txt"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := "some test data"; string(b) != expected {
|
||||
t.Fatalf("Wrong file data, expected %q, got %q", expected, string(b))
|
||||
}
|
||||
|
||||
if err := mount2.Unmount(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := ls.ReleaseRWLayer(mount2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLayerRelease(t *testing.T) {
|
||||
// TODO Windows: Figure out why this is failing
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Failing on Windows")
|
||||
}
|
||||
ls, _, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
layer1, err := createLayer(ls, "", initWithFiles(newTestFile("layer1.txt", []byte("layer 1 file"), 0644)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer2, err := createLayer(ls, layer1.ChainID(), initWithFiles(newTestFile("layer2.txt", []byte("layer 2 file"), 0644)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := ls.Release(layer1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer3a, err := createLayer(ls, layer2.ChainID(), initWithFiles(newTestFile("layer3.txt", []byte("layer 3a file"), 0644)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer3b, err := createLayer(ls, layer2.ChainID(), initWithFiles(newTestFile("layer3.txt", []byte("layer 3b file"), 0644)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := ls.Release(layer2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("Layer1: %s", layer1.ChainID())
|
||||
t.Logf("Layer2: %s", layer2.ChainID())
|
||||
t.Logf("Layer3a: %s", layer3a.ChainID())
|
||||
t.Logf("Layer3b: %s", layer3b.ChainID())
|
||||
|
||||
if expected := 4; len(ls.(*layerStore).layerMap) != expected {
|
||||
t.Fatalf("Unexpected number of layers %d, expected %d", len(ls.(*layerStore).layerMap), expected)
|
||||
}
|
||||
|
||||
releaseAndCheckDeleted(t, ls, layer3b, layer3b)
|
||||
releaseAndCheckDeleted(t, ls, layer3a, layer3a, layer2, layer1)
|
||||
}
|
||||
|
||||
func TestStoreRestore(t *testing.T) {
|
||||
// TODO Windows: Figure out why this is failing
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Failing on Windows")
|
||||
}
|
||||
ls, _, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
layer1, err := createLayer(ls, "", initWithFiles(newTestFile("layer1.txt", []byte("layer 1 file"), 0644)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer2, err := createLayer(ls, layer1.ChainID(), initWithFiles(newTestFile("layer2.txt", []byte("layer 2 file"), 0644)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := ls.Release(layer1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer3, err := createLayer(ls, layer2.ChainID(), initWithFiles(newTestFile("layer3.txt", []byte("layer 3 file"), 0644)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := ls.Release(layer2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
m, err := ls.CreateRWLayer("some-mount_name", layer3.ChainID(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pathFS, err := m.Mount("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := driver.WriteFile(pathFS, pathFS.Join(pathFS.Path(), "testfile.txt"), []byte("nothing here"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := m.Unmount(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ls2, err := newStoreFromGraphDriver(ls.(*layerStore).store.root, ls.(*layerStore).driver, runtime.GOOS)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer3b, err := ls2.Get(layer3.ChainID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertLayerEqual(t, layer3b, layer3)
|
||||
|
||||
// Create again with same name, should return error
|
||||
if _, err := ls2.CreateRWLayer("some-mount_name", layer3b.ChainID(), nil); err == nil {
|
||||
t.Fatal("Expected error creating mount with same name")
|
||||
} else if err != ErrMountNameConflict {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
m2, err := ls2.GetRWLayer("some-mount_name")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if mountPath, err := m2.Mount(""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if pathFS.Path() != mountPath.Path() {
|
||||
t.Fatalf("Unexpected path %s, expected %s", mountPath.Path(), pathFS.Path())
|
||||
}
|
||||
|
||||
if mountPath, err := m2.Mount(""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if pathFS.Path() != mountPath.Path() {
|
||||
t.Fatalf("Unexpected path %s, expected %s", mountPath.Path(), pathFS.Path())
|
||||
}
|
||||
if err := m2.Unmount(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b, err := driver.ReadFile(pathFS, pathFS.Join(pathFS.Path(), "testfile.txt"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if expected := "nothing here"; string(b) != expected {
|
||||
t.Fatalf("Unexpected content %q, expected %q", string(b), expected)
|
||||
}
|
||||
|
||||
if err := m2.Unmount(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if metadata, err := ls2.ReleaseRWLayer(m2); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(metadata) != 0 {
|
||||
t.Fatalf("Unexpectedly deleted layers: %#v", metadata)
|
||||
}
|
||||
|
||||
if metadata, err := ls2.ReleaseRWLayer(m2); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(metadata) != 0 {
|
||||
t.Fatalf("Unexpectedly deleted layers: %#v", metadata)
|
||||
}
|
||||
|
||||
releaseAndCheckDeleted(t, ls2, layer3b, layer3, layer2, layer1)
|
||||
}
|
||||
|
||||
func TestTarStreamStability(t *testing.T) {
|
||||
// TODO Windows: Figure out why this is failing
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Failing on Windows")
|
||||
}
|
||||
ls, _, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
files1 := []FileApplier{
|
||||
newTestFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
|
||||
newTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
|
||||
}
|
||||
addedFile := newTestFile("/etc/shadow", []byte("root:::::::"), 0644)
|
||||
files2 := []FileApplier{
|
||||
newTestFile("/etc/hosts", []byte("mydomain 10.0.0.2"), 0644),
|
||||
newTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0664),
|
||||
newTestFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644),
|
||||
}
|
||||
|
||||
tar1, err := tarFromFiles(files1...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tar2, err := tarFromFiles(files2...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer1, err := ls.Register(bytes.NewReader(tar1), "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// hack layer to add file
|
||||
p, err := ls.(*layerStore).driver.Get(layer1.(*referencedCacheLayer).cacheID, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := addedFile.ApplyFile(p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ls.(*layerStore).driver.Put(layer1.(*referencedCacheLayer).cacheID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer2, err := ls.Register(bytes.NewReader(tar2), layer1.ChainID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
id1 := layer1.ChainID()
|
||||
t.Logf("Layer 1: %s", layer1.ChainID())
|
||||
t.Logf("Layer 2: %s", layer2.ChainID())
|
||||
|
||||
if _, err := ls.Release(layer1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertLayerDiff(t, tar2, layer2)
|
||||
|
||||
layer1b, err := ls.Get(id1)
|
||||
if err != nil {
|
||||
t.Logf("Content of layer map: %#v", ls.(*layerStore).layerMap)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := ls.Release(layer2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertLayerDiff(t, tar1, layer1b)
|
||||
|
||||
if _, err := ls.Release(layer1b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertLayerDiff(t *testing.T, expected []byte, layer Layer) {
|
||||
expectedDigest := digest.FromBytes(expected)
|
||||
|
||||
if digest.Digest(layer.DiffID()) != expectedDigest {
|
||||
t.Fatalf("Mismatched diff id for %s, got %s, expected %s", layer.ChainID(), layer.DiffID(), expected)
|
||||
}
|
||||
|
||||
ts, err := layer.TarStream()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ts.Close()
|
||||
|
||||
actual, err := ioutil.ReadAll(ts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(actual) != len(expected) {
|
||||
logByteDiff(t, actual, expected)
|
||||
t.Fatalf("Mismatched tar stream size for %s, got %d, expected %d", layer.ChainID(), len(actual), len(expected))
|
||||
}
|
||||
|
||||
actualDigest := digest.FromBytes(actual)
|
||||
|
||||
if actualDigest != expectedDigest {
|
||||
logByteDiff(t, actual, expected)
|
||||
t.Fatalf("Wrong digest of tar stream, got %s, expected %s", actualDigest, expectedDigest)
|
||||
}
|
||||
}
|
||||
|
||||
const maxByteLog = 4 * 1024
|
||||
|
||||
func logByteDiff(t *testing.T, actual, expected []byte) {
|
||||
d1, d2 := byteDiff(actual, expected)
|
||||
if len(d1) == 0 && len(d2) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
prefix := len(actual) - len(d1)
|
||||
if len(d1) > maxByteLog || len(d2) > maxByteLog {
|
||||
t.Logf("Byte diff after %d matching bytes", prefix)
|
||||
} else {
|
||||
t.Logf("Byte diff after %d matching bytes\nActual bytes after prefix:\n%x\nExpected bytes after prefix:\n%x", prefix, d1, d2)
|
||||
}
|
||||
}
|
||||
|
||||
// byteDiff returns the differing bytes after the matching prefix
|
||||
func byteDiff(b1, b2 []byte) ([]byte, []byte) {
|
||||
i := 0
|
||||
for i < len(b1) && i < len(b2) {
|
||||
if b1[i] != b2[i] {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
return b1[i:], b2[i:]
|
||||
}
|
||||
|
||||
func tarFromFiles(files ...FileApplier) ([]byte, error) {
|
||||
td, err := ioutil.TempDir("", "tar-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
for _, f := range files {
|
||||
if err := f.ApplyFile(containerfs.NewLocalContainerFS(td)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
r, err := archive.Tar(td, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
if _, err := io.Copy(buf, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// assertReferences asserts that all the references are to the same
|
||||
// image and represent the full set of references to that image.
|
||||
func assertReferences(t *testing.T, references ...Layer) {
|
||||
if len(references) == 0 {
|
||||
return
|
||||
}
|
||||
base := references[0].(*referencedCacheLayer).roLayer
|
||||
seenReferences := map[Layer]struct{}{
|
||||
references[0]: {},
|
||||
}
|
||||
for i := 1; i < len(references); i++ {
|
||||
other := references[i].(*referencedCacheLayer).roLayer
|
||||
if base != other {
|
||||
t.Fatalf("Unexpected referenced cache layer %s, expecting %s", other.ChainID(), base.ChainID())
|
||||
}
|
||||
if _, ok := base.references[references[i]]; !ok {
|
||||
t.Fatalf("Reference not part of reference list: %v", references[i])
|
||||
}
|
||||
if _, ok := seenReferences[references[i]]; ok {
|
||||
t.Fatalf("Duplicated reference %v", references[i])
|
||||
}
|
||||
}
|
||||
if rc := len(base.references); rc != len(references) {
|
||||
t.Fatalf("Unexpected number of references %d, expecting %d", rc, len(references))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterExistingLayer(t *testing.T) {
|
||||
ls, _, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
baseFiles := []FileApplier{
|
||||
newTestFile("/etc/profile", []byte("# Base configuration"), 0644),
|
||||
}
|
||||
|
||||
layerFiles := []FileApplier{
|
||||
newTestFile("/root/.bashrc", []byte("# Root configuration"), 0644),
|
||||
}
|
||||
|
||||
li := initWithFiles(baseFiles...)
|
||||
layer1, err := createLayer(ls, "", li)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tar1, err := tarFromFiles(layerFiles...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer2a, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer2b, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertReferences(t, layer2a, layer2b)
|
||||
}
|
||||
|
||||
func TestTarStreamVerification(t *testing.T) {
|
||||
// TODO Windows: Figure out why this is failing
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Failing on Windows")
|
||||
}
|
||||
ls, tmpdir, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
files1 := []FileApplier{
|
||||
newTestFile("/foo", []byte("abc"), 0644),
|
||||
newTestFile("/bar", []byte("def"), 0644),
|
||||
}
|
||||
files2 := []FileApplier{
|
||||
newTestFile("/foo", []byte("abc"), 0644),
|
||||
newTestFile("/bar", []byte("def"), 0600), // different perm
|
||||
}
|
||||
|
||||
tar1, err := tarFromFiles(files1...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tar2, err := tarFromFiles(files2...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer1, err := ls.Register(bytes.NewReader(tar1), "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer2, err := ls.Register(bytes.NewReader(tar2), "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id1 := digest.Digest(layer1.ChainID())
|
||||
id2 := digest.Digest(layer2.ChainID())
|
||||
|
||||
// Replace tar data files
|
||||
src, err := os.Open(filepath.Join(tmpdir, id1.Algorithm().String(), id1.Hex(), "tar-split.json.gz"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.Create(filepath.Join(tmpdir, id2.Algorithm().String(), id2.Hex(), "tar-split.json.gz"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
src.Sync()
|
||||
dst.Sync()
|
||||
|
||||
ts, err := layer2.TarStream()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = io.Copy(ioutil.Discard, ts)
|
||||
if err == nil {
|
||||
t.Fatal("expected data verification to fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "could not verify layer data") {
|
||||
t.Fatalf("wrong error returned from tarstream: %q", err)
|
||||
}
|
||||
}
|
9
vendor/github.com/docker/docker-ce/components/engine/layer/layer_unix.go
generated
vendored
Normal file
9
vendor/github.com/docker/docker-ce/components/engine/layer/layer_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
// +build linux freebsd darwin openbsd
|
||||
|
||||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import "github.com/docker/docker/pkg/stringid"
|
||||
|
||||
func (ls *layerStore) mountID(name string) string {
|
||||
return stringid.GenerateRandomID()
|
||||
}
|
73
vendor/github.com/docker/docker-ce/components/engine/layer/layer_unix_test.go
generated
vendored
Normal file
73
vendor/github.com/docker/docker-ce/components/engine/layer/layer_unix_test.go
generated
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
// +build !windows
|
||||
|
||||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func graphDiffSize(ls Store, l Layer) (int64, error) {
|
||||
cl := getCachedLayer(l)
|
||||
var parent string
|
||||
if cl.parent != nil {
|
||||
parent = cl.parent.cacheID
|
||||
}
|
||||
return ls.(*layerStore).driver.DiffSize(cl.cacheID, parent)
|
||||
}
|
||||
|
||||
// Unix as Windows graph driver does not support Changes which is indirectly
|
||||
// invoked by calling DiffSize on the driver
|
||||
func TestLayerSize(t *testing.T) {
|
||||
ls, _, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
content1 := []byte("Base contents")
|
||||
content2 := []byte("Added contents")
|
||||
|
||||
layer1, err := createLayer(ls, "", initWithFiles(newTestFile("file1", content1, 0644)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer2, err := createLayer(ls, layer1.ChainID(), initWithFiles(newTestFile("file2", content2, 0644)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer1DiffSize, err := graphDiffSize(ls, layer1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if int(layer1DiffSize) != len(content1) {
|
||||
t.Fatalf("Unexpected diff size %d, expected %d", layer1DiffSize, len(content1))
|
||||
}
|
||||
|
||||
layer1Size, err := layer1.Size()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := len(content1); int(layer1Size) != expected {
|
||||
t.Fatalf("Unexpected size %d, expected %d", layer1Size, expected)
|
||||
}
|
||||
|
||||
layer2DiffSize, err := graphDiffSize(ls, layer2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if int(layer2DiffSize) != len(content2) {
|
||||
t.Fatalf("Unexpected diff size %d, expected %d", layer2DiffSize, len(content2))
|
||||
}
|
||||
|
||||
layer2Size, err := layer2.Size()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := len(content1) + len(content2); int(layer2Size) != expected {
|
||||
t.Fatalf("Unexpected size %d, expected %d", layer2Size, expected)
|
||||
}
|
||||
|
||||
}
|
46
vendor/github.com/docker/docker-ce/components/engine/layer/layer_windows.go
generated
vendored
Normal file
46
vendor/github.com/docker/docker-ce/components/engine/layer/layer_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Getter is an interface to get the path to a layer on the host.
|
||||
type Getter interface {
|
||||
// GetLayerPath gets the path for the layer. This is different from Get()
|
||||
// since that returns an interface to account for umountable layers.
|
||||
GetLayerPath(id string) (string, error)
|
||||
}
|
||||
|
||||
// GetLayerPath returns the path to a layer
|
||||
func GetLayerPath(s Store, layer ChainID) (string, error) {
|
||||
ls, ok := s.(*layerStore)
|
||||
if !ok {
|
||||
return "", errors.New("unsupported layer store")
|
||||
}
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
|
||||
rl, ok := ls.layerMap[layer]
|
||||
if !ok {
|
||||
return "", ErrLayerDoesNotExist
|
||||
}
|
||||
|
||||
if layerGetter, ok := ls.driver.(Getter); ok {
|
||||
return layerGetter.GetLayerPath(rl.cacheID)
|
||||
}
|
||||
path, err := ls.driver.Get(rl.cacheID, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := ls.driver.Put(rl.cacheID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path.Path(), nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) mountID(name string) string {
|
||||
// windows has issues if container ID doesn't match mount ID
|
||||
return name
|
||||
}
|
252
vendor/github.com/docker/docker-ce/components/engine/layer/migration.go
generated
vendored
Normal file
252
vendor/github.com/docker/docker-ce/components/engine/layer/migration.go
generated
vendored
Normal file
|
@ -0,0 +1,252 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vbatts/tar-split/tar/asm"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
)
|
||||
|
||||
// CreateRWLayerByGraphID creates a RWLayer in the layer store using
|
||||
// the provided name with the given graphID. To get the RWLayer
|
||||
// after migration the layer may be retrieved by the given name.
|
||||
func (ls *layerStore) CreateRWLayerByGraphID(name, graphID string, parent ChainID) (err error) {
|
||||
ls.mountL.Lock()
|
||||
defer ls.mountL.Unlock()
|
||||
m, ok := ls.mounts[name]
|
||||
if ok {
|
||||
if m.parent.chainID != parent {
|
||||
return errors.New("name conflict, mismatched parent")
|
||||
}
|
||||
if m.mountID != graphID {
|
||||
return errors.New("mount already exists")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ls.driver.Exists(graphID) {
|
||||
return fmt.Errorf("graph ID does not exist: %q", graphID)
|
||||
}
|
||||
|
||||
var p *roLayer
|
||||
if string(parent) != "" {
|
||||
p = ls.get(parent)
|
||||
if p == nil {
|
||||
return ErrLayerDoesNotExist
|
||||
}
|
||||
|
||||
// Release parent chain if error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ls.layerL.Lock()
|
||||
ls.releaseLayer(p)
|
||||
ls.layerL.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// TODO: Ensure graphID has correct parent
|
||||
|
||||
m = &mountedLayer{
|
||||
name: name,
|
||||
parent: p,
|
||||
mountID: graphID,
|
||||
layerStore: ls,
|
||||
references: map[RWLayer]*referencedRWLayer{},
|
||||
}
|
||||
|
||||
// Check for existing init layer
|
||||
initID := fmt.Sprintf("%s-init", graphID)
|
||||
if ls.driver.Exists(initID) {
|
||||
m.initID = initID
|
||||
}
|
||||
|
||||
return ls.saveMount(m)
|
||||
}
|
||||
|
||||
func (ls *layerStore) ChecksumForGraphID(id, parent, oldTarDataPath, newTarDataPath string) (diffID DiffID, size int64, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logrus.Debugf("could not get checksum for %q with tar-split: %q", id, err)
|
||||
diffID, size, err = ls.checksumForGraphIDNoTarsplit(id, parent, newTarDataPath)
|
||||
}
|
||||
}()
|
||||
|
||||
if oldTarDataPath == "" {
|
||||
err = errors.New("no tar-split file")
|
||||
return
|
||||
}
|
||||
|
||||
tarDataFile, err := os.Open(oldTarDataPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer tarDataFile.Close()
|
||||
uncompressed, err := gzip.NewReader(tarDataFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dgst := digest.Canonical.Digester()
|
||||
err = ls.assembleTarTo(id, uncompressed, &size, dgst.Hash())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
diffID = DiffID(dgst.Digest())
|
||||
err = os.RemoveAll(newTarDataPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = os.Link(oldTarDataPath, newTarDataPath)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ls *layerStore) checksumForGraphIDNoTarsplit(id, parent, newTarDataPath string) (diffID DiffID, size int64, err error) {
|
||||
rawarchive, err := ls.driver.Diff(id, parent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rawarchive.Close()
|
||||
|
||||
f, err := os.Create(newTarDataPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
mfz := gzip.NewWriter(f)
|
||||
defer mfz.Close()
|
||||
metaPacker := storage.NewJSONPacker(mfz)
|
||||
|
||||
packerCounter := &packSizeCounter{metaPacker, &size}
|
||||
|
||||
archive, err := asm.NewInputTarStream(rawarchive, packerCounter, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dgst, err := digest.FromReader(archive)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
diffID = DiffID(dgst)
|
||||
return
|
||||
}
|
||||
|
||||
func (ls *layerStore) RegisterByGraphID(graphID string, parent ChainID, diffID DiffID, tarDataFile string, size int64) (Layer, error) {
|
||||
// err is used to hold the error which will always trigger
|
||||
// cleanup of creates sources but may not be an error returned
|
||||
// to the caller (already exists).
|
||||
var err error
|
||||
var p *roLayer
|
||||
if string(parent) != "" {
|
||||
p = ls.get(parent)
|
||||
if p == nil {
|
||||
return nil, ErrLayerDoesNotExist
|
||||
}
|
||||
|
||||
// Release parent chain if error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ls.layerL.Lock()
|
||||
ls.releaseLayer(p)
|
||||
ls.layerL.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Create new roLayer
|
||||
layer := &roLayer{
|
||||
parent: p,
|
||||
cacheID: graphID,
|
||||
referenceCount: 1,
|
||||
layerStore: ls,
|
||||
references: map[Layer]struct{}{},
|
||||
diffID: diffID,
|
||||
size: size,
|
||||
chainID: createChainIDFromParent(parent, diffID),
|
||||
}
|
||||
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
|
||||
if existingLayer := ls.getWithoutLock(layer.chainID); existingLayer != nil {
|
||||
// Set error for cleanup, but do not return
|
||||
err = errors.New("layer already exists")
|
||||
return existingLayer.getReference(), nil
|
||||
}
|
||||
|
||||
tx, err := ls.store.StartTransaction()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logrus.Debugf("Cleaning up transaction after failed migration for %s: %v", graphID, err)
|
||||
if err := tx.Cancel(); err != nil {
|
||||
logrus.Errorf("Error canceling metadata transaction %q: %s", tx.String(), err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
tsw, err := tx.TarSplitWriter(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tsw.Close()
|
||||
tdf, err := os.Open(tarDataFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tdf.Close()
|
||||
_, err = io.Copy(tsw, tdf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = storeLayer(tx, layer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = tx.Commit(layer.chainID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ls.layerMap[layer.chainID] = layer
|
||||
|
||||
return layer.getReference(), nil
|
||||
}
|
||||
|
||||
type unpackSizeCounter struct {
|
||||
unpacker storage.Unpacker
|
||||
size *int64
|
||||
}
|
||||
|
||||
func (u *unpackSizeCounter) Next() (*storage.Entry, error) {
|
||||
e, err := u.unpacker.Next()
|
||||
if err == nil && u.size != nil {
|
||||
*u.size += e.Size
|
||||
}
|
||||
return e, err
|
||||
}
|
||||
|
||||
type packSizeCounter struct {
|
||||
packer storage.Packer
|
||||
size *int64
|
||||
}
|
||||
|
||||
func (p *packSizeCounter) AddEntry(e storage.Entry) (int, error) {
|
||||
n, err := p.packer.AddEntry(e)
|
||||
if err == nil && p.size != nil {
|
||||
*p.size += e.Size
|
||||
}
|
||||
return n, err
|
||||
}
|
429
vendor/github.com/docker/docker-ce/components/engine/layer/migration_test.go
generated
vendored
Normal file
429
vendor/github.com/docker/docker-ce/components/engine/layer/migration_test.go
generated
vendored
Normal file
|
@ -0,0 +1,429 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/vbatts/tar-split/tar/asm"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
)
|
||||
|
||||
func writeTarSplitFile(name string, tarContent []byte) error {
|
||||
f, err := os.OpenFile(name, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fz := gzip.NewWriter(f)
|
||||
|
||||
metaPacker := storage.NewJSONPacker(fz)
|
||||
defer fz.Close()
|
||||
|
||||
rdr, err := asm.NewInputTarStream(bytes.NewReader(tarContent), metaPacker, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(ioutil.Discard, rdr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestLayerMigration(t *testing.T) {
|
||||
// TODO Windows: Figure out why this is failing
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Failing on Windows")
|
||||
}
|
||||
td, err := ioutil.TempDir("", "migration-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
layer1Files := []FileApplier{
|
||||
newTestFile("/root/.bashrc", []byte("# Boring configuration"), 0644),
|
||||
newTestFile("/etc/profile", []byte("# Base configuration"), 0644),
|
||||
}
|
||||
|
||||
layer2Files := []FileApplier{
|
||||
newTestFile("/root/.bashrc", []byte("# Updated configuration"), 0644),
|
||||
}
|
||||
|
||||
tar1, err := tarFromFiles(layer1Files...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tar2, err := tarFromFiles(layer2Files...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
graph, err := newVFSGraphDriver(filepath.Join(td, "graphdriver-"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
graphID1 := stringid.GenerateRandomID()
|
||||
if err := graph.Create(graphID1, "", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := graph.ApplyDiff(graphID1, "", bytes.NewReader(tar1)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tf1 := filepath.Join(td, "tar1.json.gz")
|
||||
if err := writeTarSplitFile(tf1, tar1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
root := filepath.Join(td, "layers")
|
||||
ls, err := newStoreFromGraphDriver(root, graph, runtime.GOOS)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newTarDataPath := filepath.Join(td, ".migration-tardata")
|
||||
diffID, size, err := ls.(*layerStore).ChecksumForGraphID(graphID1, "", tf1, newTarDataPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer1a, err := ls.(*layerStore).RegisterByGraphID(graphID1, "", diffID, newTarDataPath, size)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer1b, err := ls.Register(bytes.NewReader(tar1), "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertReferences(t, layer1a, layer1b)
|
||||
// Attempt register, should be same
|
||||
layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
graphID2 := stringid.GenerateRandomID()
|
||||
if err := graph.Create(graphID2, graphID1, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := graph.ApplyDiff(graphID2, graphID1, bytes.NewReader(tar2)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tf2 := filepath.Join(td, "tar2.json.gz")
|
||||
if err := writeTarSplitFile(tf2, tar2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
diffID, size, err = ls.(*layerStore).ChecksumForGraphID(graphID2, graphID1, tf2, newTarDataPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer2b, err := ls.(*layerStore).RegisterByGraphID(graphID2, layer1a.ChainID(), diffID, tf2, size)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertReferences(t, layer2a, layer2b)
|
||||
|
||||
if metadata, err := ls.Release(layer2a); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(metadata) > 0 {
|
||||
t.Fatalf("Unexpected layer removal after first release: %#v", metadata)
|
||||
}
|
||||
|
||||
metadata, err := ls.Release(layer2b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertMetadata(t, metadata, createMetadata(layer2a))
|
||||
}
|
||||
|
||||
func tarFromFilesInGraph(graph graphdriver.Driver, graphID, parentID string, files ...FileApplier) ([]byte, error) {
|
||||
t, err := tarFromFiles(files...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := graph.Create(graphID, parentID, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := graph.ApplyDiff(graphID, parentID, bytes.NewReader(t)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ar, err := graph.Diff(graphID, parentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer ar.Close()
|
||||
|
||||
return ioutil.ReadAll(ar)
|
||||
}
|
||||
|
||||
func TestLayerMigrationNoTarsplit(t *testing.T) {
|
||||
// TODO Windows: Figure out why this is failing
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Failing on Windows")
|
||||
}
|
||||
td, err := ioutil.TempDir("", "migration-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
layer1Files := []FileApplier{
|
||||
newTestFile("/root/.bashrc", []byte("# Boring configuration"), 0644),
|
||||
newTestFile("/etc/profile", []byte("# Base configuration"), 0644),
|
||||
}
|
||||
|
||||
layer2Files := []FileApplier{
|
||||
newTestFile("/root/.bashrc", []byte("# Updated configuration"), 0644),
|
||||
}
|
||||
|
||||
graph, err := newVFSGraphDriver(filepath.Join(td, "graphdriver-"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
graphID1 := stringid.GenerateRandomID()
|
||||
graphID2 := stringid.GenerateRandomID()
|
||||
|
||||
tar1, err := tarFromFilesInGraph(graph, graphID1, "", layer1Files...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tar2, err := tarFromFilesInGraph(graph, graphID2, graphID1, layer2Files...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
root := filepath.Join(td, "layers")
|
||||
ls, err := newStoreFromGraphDriver(root, graph, runtime.GOOS)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newTarDataPath := filepath.Join(td, ".migration-tardata")
|
||||
diffID, size, err := ls.(*layerStore).ChecksumForGraphID(graphID1, "", "", newTarDataPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer1a, err := ls.(*layerStore).RegisterByGraphID(graphID1, "", diffID, newTarDataPath, size)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer1b, err := ls.Register(bytes.NewReader(tar1), "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertReferences(t, layer1a, layer1b)
|
||||
|
||||
// Attempt register, should be same
|
||||
layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffID, size, err = ls.(*layerStore).ChecksumForGraphID(graphID2, graphID1, "", newTarDataPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer2b, err := ls.(*layerStore).RegisterByGraphID(graphID2, layer1a.ChainID(), diffID, newTarDataPath, size)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertReferences(t, layer2a, layer2b)
|
||||
|
||||
if metadata, err := ls.Release(layer2a); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(metadata) > 0 {
|
||||
t.Fatalf("Unexpected layer removal after first release: %#v", metadata)
|
||||
}
|
||||
|
||||
metadata, err := ls.Release(layer2b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertMetadata(t, metadata, createMetadata(layer2a))
|
||||
}
|
||||
|
||||
func TestMountMigration(t *testing.T) {
|
||||
// TODO Windows: Figure out why this is failing (obvious - paths... needs porting)
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Failing on Windows")
|
||||
}
|
||||
ls, _, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
baseFiles := []FileApplier{
|
||||
newTestFile("/root/.bashrc", []byte("# Boring configuration"), 0644),
|
||||
newTestFile("/etc/profile", []byte("# Base configuration"), 0644),
|
||||
}
|
||||
initFiles := []FileApplier{
|
||||
newTestFile("/etc/hosts", []byte{}, 0644),
|
||||
newTestFile("/etc/resolv.conf", []byte{}, 0644),
|
||||
}
|
||||
mountFiles := []FileApplier{
|
||||
newTestFile("/etc/hosts", []byte("localhost 127.0.0.1"), 0644),
|
||||
newTestFile("/root/.bashrc", []byte("# Updated configuration"), 0644),
|
||||
newTestFile("/root/testfile1.txt", []byte("nothing valuable"), 0644),
|
||||
}
|
||||
|
||||
initTar, err := tarFromFiles(initFiles...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mountTar, err := tarFromFiles(mountFiles...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
graph := ls.(*layerStore).driver
|
||||
|
||||
layer1, err := createLayer(ls, "", initWithFiles(baseFiles...))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
graphID1 := layer1.(*referencedCacheLayer).cacheID
|
||||
|
||||
containerID := stringid.GenerateRandomID()
|
||||
containerInit := fmt.Sprintf("%s-init", containerID)
|
||||
|
||||
if err := graph.Create(containerInit, graphID1, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := graph.ApplyDiff(containerInit, graphID1, bytes.NewReader(initTar)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := graph.Create(containerID, containerInit, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := graph.ApplyDiff(containerID, containerInit, bytes.NewReader(mountTar)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ls.(*layerStore).CreateRWLayerByGraphID("migration-mount", containerID, layer1.ChainID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rwLayer1, err := ls.GetRWLayer("migration-mount")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := rwLayer1.Mount(""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changes, err := rwLayer1.Changes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := 5; len(changes) != expected {
|
||||
t.Logf("Changes %#v", changes)
|
||||
t.Fatalf("Wrong number of changes %d, expected %d", len(changes), expected)
|
||||
}
|
||||
|
||||
sortChanges(changes)
|
||||
|
||||
assertChange(t, changes[0], archive.Change{
|
||||
Path: "/etc",
|
||||
Kind: archive.ChangeModify,
|
||||
})
|
||||
assertChange(t, changes[1], archive.Change{
|
||||
Path: "/etc/hosts",
|
||||
Kind: archive.ChangeModify,
|
||||
})
|
||||
assertChange(t, changes[2], archive.Change{
|
||||
Path: "/root",
|
||||
Kind: archive.ChangeModify,
|
||||
})
|
||||
assertChange(t, changes[3], archive.Change{
|
||||
Path: "/root/.bashrc",
|
||||
Kind: archive.ChangeModify,
|
||||
})
|
||||
assertChange(t, changes[4], archive.Change{
|
||||
Path: "/root/testfile1.txt",
|
||||
Kind: archive.ChangeAdd,
|
||||
})
|
||||
|
||||
if _, err := ls.CreateRWLayer("migration-mount", layer1.ChainID(), nil); err == nil {
|
||||
t.Fatal("Expected error creating mount with same name")
|
||||
} else if err != ErrMountNameConflict {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rwLayer2, err := ls.GetRWLayer("migration-mount")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if getMountLayer(rwLayer1) != getMountLayer(rwLayer2) {
|
||||
t.Fatal("Expected same layer from get with same name as from migrate")
|
||||
}
|
||||
|
||||
if _, err := rwLayer2.Mount(""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := rwLayer2.Mount(""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if metadata, err := ls.Release(layer1); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(metadata) > 0 {
|
||||
t.Fatalf("Expected no layers to be deleted, deleted %#v", metadata)
|
||||
}
|
||||
|
||||
if err := rwLayer1.Unmount(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := ls.ReleaseRWLayer(rwLayer1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := rwLayer2.Unmount(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := rwLayer2.Unmount(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
metadata, err := ls.ReleaseRWLayer(rwLayer2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(metadata) == 0 {
|
||||
t.Fatal("Expected base layer to be deleted when deleting mount")
|
||||
}
|
||||
|
||||
assertMetadata(t, metadata, createMetadata(layer1))
|
||||
}
|
239
vendor/github.com/docker/docker-ce/components/engine/layer/mount_test.go
generated
vendored
Normal file
239
vendor/github.com/docker/docker-ce/components/engine/layer/mount_test.go
generated
vendored
Normal file
|
@ -0,0 +1,239 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/continuity/driver"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/containerfs"
|
||||
)
|
||||
|
||||
func TestMountInit(t *testing.T) {
|
||||
// TODO Windows: Figure out why this is failing
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Failing on Windows")
|
||||
}
|
||||
ls, _, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
basefile := newTestFile("testfile.txt", []byte("base data!"), 0644)
|
||||
initfile := newTestFile("testfile.txt", []byte("init data!"), 0777)
|
||||
|
||||
li := initWithFiles(basefile)
|
||||
layer, err := createLayer(ls, "", li)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mountInit := func(root containerfs.ContainerFS) error {
|
||||
return initfile.ApplyFile(root)
|
||||
}
|
||||
|
||||
rwLayerOpts := &CreateRWLayerOpts{
|
||||
InitFunc: mountInit,
|
||||
}
|
||||
m, err := ls.CreateRWLayer("fun-mount", layer.ChainID(), rwLayerOpts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pathFS, err := m.Mount("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fi, err := pathFS.Stat(pathFS.Join(pathFS.Path(), "testfile.txt"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
f, err := pathFS.Open(pathFS.Join(pathFS.Path(), "testfile.txt"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := "init data!"; string(b) != expected {
|
||||
t.Fatalf("Unexpected test file contents %q, expected %q", string(b), expected)
|
||||
}
|
||||
|
||||
if fi.Mode().Perm() != 0777 {
|
||||
t.Fatalf("Unexpected filemode %o, expecting %o", fi.Mode().Perm(), 0777)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountSize(t *testing.T) {
|
||||
// TODO Windows: Figure out why this is failing
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Failing on Windows")
|
||||
}
|
||||
ls, _, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
content1 := []byte("Base contents")
|
||||
content2 := []byte("Mutable contents")
|
||||
contentInit := []byte("why am I excluded from the size ☹")
|
||||
|
||||
li := initWithFiles(newTestFile("file1", content1, 0644))
|
||||
layer, err := createLayer(ls, "", li)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mountInit := func(root containerfs.ContainerFS) error {
|
||||
return newTestFile("file-init", contentInit, 0777).ApplyFile(root)
|
||||
}
|
||||
rwLayerOpts := &CreateRWLayerOpts{
|
||||
InitFunc: mountInit,
|
||||
}
|
||||
|
||||
m, err := ls.CreateRWLayer("mount-size", layer.ChainID(), rwLayerOpts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pathFS, err := m.Mount("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := driver.WriteFile(pathFS, pathFS.Join(pathFS.Path(), "file2"), content2, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mountSize, err := m.Size()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := len(content2); int(mountSize) != expected {
|
||||
t.Fatalf("Unexpected mount size %d, expected %d", int(mountSize), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountChanges(t *testing.T) {
|
||||
// TODO Windows: Figure out why this is failing
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Failing on Windows")
|
||||
}
|
||||
ls, _, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
basefiles := []FileApplier{
|
||||
newTestFile("testfile1.txt", []byte("base data!"), 0644),
|
||||
newTestFile("testfile2.txt", []byte("base data!"), 0644),
|
||||
newTestFile("testfile3.txt", []byte("base data!"), 0644),
|
||||
}
|
||||
initfile := newTestFile("testfile1.txt", []byte("init data!"), 0777)
|
||||
|
||||
li := initWithFiles(basefiles...)
|
||||
layer, err := createLayer(ls, "", li)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mountInit := func(root containerfs.ContainerFS) error {
|
||||
return initfile.ApplyFile(root)
|
||||
}
|
||||
rwLayerOpts := &CreateRWLayerOpts{
|
||||
InitFunc: mountInit,
|
||||
}
|
||||
|
||||
m, err := ls.CreateRWLayer("mount-changes", layer.ChainID(), rwLayerOpts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pathFS, err := m.Mount("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := pathFS.Lchmod(pathFS.Join(pathFS.Path(), "testfile1.txt"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := driver.WriteFile(pathFS, pathFS.Join(pathFS.Path(), "testfile1.txt"), []byte("mount data!"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := pathFS.Remove(pathFS.Join(pathFS.Path(), "testfile2.txt")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := pathFS.Lchmod(pathFS.Join(pathFS.Path(), "testfile3.txt"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := driver.WriteFile(pathFS, pathFS.Join(pathFS.Path(), "testfile4.txt"), []byte("mount data!"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changes, err := m.Changes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := 4; len(changes) != expected {
|
||||
t.Fatalf("Wrong number of changes %d, expected %d", len(changes), expected)
|
||||
}
|
||||
|
||||
sortChanges(changes)
|
||||
|
||||
assertChange(t, changes[0], archive.Change{
|
||||
Path: "/testfile1.txt",
|
||||
Kind: archive.ChangeModify,
|
||||
})
|
||||
assertChange(t, changes[1], archive.Change{
|
||||
Path: "/testfile2.txt",
|
||||
Kind: archive.ChangeDelete,
|
||||
})
|
||||
assertChange(t, changes[2], archive.Change{
|
||||
Path: "/testfile3.txt",
|
||||
Kind: archive.ChangeModify,
|
||||
})
|
||||
assertChange(t, changes[3], archive.Change{
|
||||
Path: "/testfile4.txt",
|
||||
Kind: archive.ChangeAdd,
|
||||
})
|
||||
}
|
||||
|
||||
func assertChange(t *testing.T, actual, expected archive.Change) {
|
||||
if actual.Path != expected.Path {
|
||||
t.Fatalf("Unexpected change path %s, expected %s", actual.Path, expected.Path)
|
||||
}
|
||||
if actual.Kind != expected.Kind {
|
||||
t.Fatalf("Unexpected change type %s, expected %s", actual.Kind, expected.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func sortChanges(changes []archive.Change) {
|
||||
cs := &changeSorter{
|
||||
changes: changes,
|
||||
}
|
||||
sort.Sort(cs)
|
||||
}
|
||||
|
||||
type changeSorter struct {
|
||||
changes []archive.Change
|
||||
}
|
||||
|
||||
func (cs *changeSorter) Len() int {
|
||||
return len(cs.changes)
|
||||
}
|
||||
|
||||
func (cs *changeSorter) Swap(i, j int) {
|
||||
cs.changes[i], cs.changes[j] = cs.changes[j], cs.changes[i]
|
||||
}
|
||||
|
||||
func (cs *changeSorter) Less(i, j int) bool {
|
||||
return cs.changes[i].Path < cs.changes[j].Path
|
||||
}
|
100
vendor/github.com/docker/docker-ce/components/engine/layer/mounted_layer.go
generated
vendored
Normal file
100
vendor/github.com/docker/docker-ce/components/engine/layer/mounted_layer.go
generated
vendored
Normal file
|
@ -0,0 +1,100 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/containerfs"
|
||||
)
|
||||
|
||||
type mountedLayer struct {
|
||||
name string
|
||||
mountID string
|
||||
initID string
|
||||
parent *roLayer
|
||||
path string
|
||||
layerStore *layerStore
|
||||
|
||||
references map[RWLayer]*referencedRWLayer
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) cacheParent() string {
|
||||
if ml.initID != "" {
|
||||
return ml.initID
|
||||
}
|
||||
if ml.parent != nil {
|
||||
return ml.parent.cacheID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) TarStream() (io.ReadCloser, error) {
|
||||
return ml.layerStore.driver.Diff(ml.mountID, ml.cacheParent())
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) Name() string {
|
||||
return ml.name
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) Parent() Layer {
|
||||
if ml.parent != nil {
|
||||
return ml.parent
|
||||
}
|
||||
|
||||
// Return a nil interface instead of an interface wrapping a nil
|
||||
// pointer.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) Size() (int64, error) {
|
||||
return ml.layerStore.driver.DiffSize(ml.mountID, ml.cacheParent())
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) Changes() ([]archive.Change, error) {
|
||||
return ml.layerStore.driver.Changes(ml.mountID, ml.cacheParent())
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) Metadata() (map[string]string, error) {
|
||||
return ml.layerStore.driver.GetMetadata(ml.mountID)
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) getReference() RWLayer {
|
||||
ref := &referencedRWLayer{
|
||||
mountedLayer: ml,
|
||||
}
|
||||
ml.references[ref] = ref
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) hasReferences() bool {
|
||||
return len(ml.references) > 0
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) deleteReference(ref RWLayer) error {
|
||||
if _, ok := ml.references[ref]; !ok {
|
||||
return ErrLayerNotRetained
|
||||
}
|
||||
delete(ml.references, ref)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) retakeReference(r RWLayer) {
|
||||
if ref, ok := r.(*referencedRWLayer); ok {
|
||||
ml.references[ref] = ref
|
||||
}
|
||||
}
|
||||
|
||||
type referencedRWLayer struct {
|
||||
*mountedLayer
|
||||
}
|
||||
|
||||
func (rl *referencedRWLayer) Mount(mountLabel string) (containerfs.ContainerFS, error) {
|
||||
return rl.layerStore.driver.Get(rl.mountedLayer.mountID, mountLabel)
|
||||
}
|
||||
|
||||
// Unmount decrements the activity count and unmounts the underlying layer
|
||||
// Callers should only call `Unmount` once per call to `Mount`, even on error.
|
||||
func (rl *referencedRWLayer) Unmount() error {
|
||||
return rl.layerStore.driver.Put(rl.mountedLayer.mountID)
|
||||
}
|
178
vendor/github.com/docker/docker-ce/components/engine/layer/ro_layer.go
generated
vendored
Normal file
178
vendor/github.com/docker/docker-ce/components/engine/layer/ro_layer.go
generated
vendored
Normal file
|
@ -0,0 +1,178 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
type roLayer struct {
|
||||
chainID ChainID
|
||||
diffID DiffID
|
||||
parent *roLayer
|
||||
cacheID string
|
||||
size int64
|
||||
layerStore *layerStore
|
||||
descriptor distribution.Descriptor
|
||||
|
||||
referenceCount int
|
||||
references map[Layer]struct{}
|
||||
}
|
||||
|
||||
// TarStream for roLayer guarantees that the data that is produced is the exact
|
||||
// data that the layer was registered with.
|
||||
func (rl *roLayer) TarStream() (io.ReadCloser, error) {
|
||||
rc, err := rl.layerStore.getTarStream(rl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vrc, err := newVerifiedReadCloser(rc, digest.Digest(rl.diffID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vrc, nil
|
||||
}
|
||||
|
||||
// TarStreamFrom does not make any guarantees to the correctness of the produced
|
||||
// data. As such it should not be used when the layer content must be verified
|
||||
// to be an exact match to the registered layer.
|
||||
func (rl *roLayer) TarStreamFrom(parent ChainID) (io.ReadCloser, error) {
|
||||
var parentCacheID string
|
||||
for pl := rl.parent; pl != nil; pl = pl.parent {
|
||||
if pl.chainID == parent {
|
||||
parentCacheID = pl.cacheID
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if parent != ChainID("") && parentCacheID == "" {
|
||||
return nil, fmt.Errorf("layer ID '%s' is not a parent of the specified layer: cannot provide diff to non-parent", parent)
|
||||
}
|
||||
return rl.layerStore.driver.Diff(rl.cacheID, parentCacheID)
|
||||
}
|
||||
|
||||
func (rl *roLayer) ChainID() ChainID {
|
||||
return rl.chainID
|
||||
}
|
||||
|
||||
func (rl *roLayer) DiffID() DiffID {
|
||||
return rl.diffID
|
||||
}
|
||||
|
||||
func (rl *roLayer) Parent() Layer {
|
||||
if rl.parent == nil {
|
||||
return nil
|
||||
}
|
||||
return rl.parent
|
||||
}
|
||||
|
||||
func (rl *roLayer) Size() (size int64, err error) {
|
||||
if rl.parent != nil {
|
||||
size, err = rl.parent.Size()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return size + rl.size, nil
|
||||
}
|
||||
|
||||
func (rl *roLayer) DiffSize() (size int64, err error) {
|
||||
return rl.size, nil
|
||||
}
|
||||
|
||||
func (rl *roLayer) Metadata() (map[string]string, error) {
|
||||
return rl.layerStore.driver.GetMetadata(rl.cacheID)
|
||||
}
|
||||
|
||||
type referencedCacheLayer struct {
|
||||
*roLayer
|
||||
}
|
||||
|
||||
func (rl *roLayer) getReference() Layer {
|
||||
ref := &referencedCacheLayer{
|
||||
roLayer: rl,
|
||||
}
|
||||
rl.references[ref] = struct{}{}
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
func (rl *roLayer) hasReference(ref Layer) bool {
|
||||
_, ok := rl.references[ref]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (rl *roLayer) hasReferences() bool {
|
||||
return len(rl.references) > 0
|
||||
}
|
||||
|
||||
func (rl *roLayer) deleteReference(ref Layer) {
|
||||
delete(rl.references, ref)
|
||||
}
|
||||
|
||||
func (rl *roLayer) depth() int {
|
||||
if rl.parent == nil {
|
||||
return 1
|
||||
}
|
||||
return rl.parent.depth() + 1
|
||||
}
|
||||
|
||||
func storeLayer(tx *fileMetadataTransaction, layer *roLayer) error {
|
||||
if err := tx.SetDiffID(layer.diffID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.SetSize(layer.size); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.SetCacheID(layer.cacheID); err != nil {
|
||||
return err
|
||||
}
|
||||
// Do not store empty descriptors
|
||||
if layer.descriptor.Digest != "" {
|
||||
if err := tx.SetDescriptor(layer.descriptor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if layer.parent != nil {
|
||||
if err := tx.SetParent(layer.parent.chainID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.setOS(layer.layerStore.os)
|
||||
}
|
||||
|
||||
func newVerifiedReadCloser(rc io.ReadCloser, dgst digest.Digest) (io.ReadCloser, error) {
|
||||
return &verifiedReadCloser{
|
||||
rc: rc,
|
||||
dgst: dgst,
|
||||
verifier: dgst.Verifier(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type verifiedReadCloser struct {
|
||||
rc io.ReadCloser
|
||||
dgst digest.Digest
|
||||
verifier digest.Verifier
|
||||
}
|
||||
|
||||
func (vrc *verifiedReadCloser) Read(p []byte) (n int, err error) {
|
||||
n, err = vrc.rc.Read(p)
|
||||
if n > 0 {
|
||||
if n, err := vrc.verifier.Write(p[:n]); err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
if !vrc.verifier.Verified() {
|
||||
err = fmt.Errorf("could not verify layer data for: %s. This may be because internal files in the layer store were modified. Re-pulling or rebuilding this image may resolve the issue", vrc.dgst)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (vrc *verifiedReadCloser) Close() error {
|
||||
return vrc.rc.Close()
|
||||
}
|
9
vendor/github.com/docker/docker-ce/components/engine/layer/ro_layer_windows.go
generated
vendored
Normal file
9
vendor/github.com/docker/docker-ce/components/engine/layer/ro_layer_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
package layer // import "github.com/docker/docker/layer"
|
||||
|
||||
import "github.com/docker/distribution"
|
||||
|
||||
var _ distribution.Describable = &roLayer{}
|
||||
|
||||
func (rl *roLayer) Descriptor() distribution.Descriptor {
|
||||
return rl.descriptor
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue