зеркало из https://github.com/microsoft/docker.git
docker/fs is deprecated by docker/graph
This commit is contained in:
Родитель
240333277a
Коммит
ea258c4492
127
fs/changes.go
127
fs/changes.go
|
@ -1,127 +0,0 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ChangeType int
|
||||
|
||||
const (
|
||||
ChangeModify = iota
|
||||
ChangeAdd
|
||||
ChangeDelete
|
||||
)
|
||||
|
||||
type Change struct {
|
||||
Path string
|
||||
Kind ChangeType
|
||||
}
|
||||
|
||||
func (change *Change) String() string {
|
||||
var kind string
|
||||
switch change.Kind {
|
||||
case ChangeModify:
|
||||
kind = "C"
|
||||
case ChangeAdd:
|
||||
kind = "A"
|
||||
case ChangeDelete:
|
||||
kind = "D"
|
||||
}
|
||||
return fmt.Sprintf("%s %s", kind, change.Path)
|
||||
}
|
||||
|
||||
func (store *Store) Changes(mp *Mountpoint) ([]Change, error) {
|
||||
var changes []Change
|
||||
image, err := store.Get(mp.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layers, err := image.layers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = filepath.Walk(mp.Rw, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
path, err = filepath.Rel(mp.Rw, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path = filepath.Join("/", path)
|
||||
|
||||
// Skip root
|
||||
if path == "/" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip AUFS metadata
|
||||
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
|
||||
return err
|
||||
}
|
||||
|
||||
change := Change{
|
||||
Path: path,
|
||||
}
|
||||
|
||||
// Find out what kind of modification happened
|
||||
file := filepath.Base(path)
|
||||
// If there is a whiteout, then the file was removed
|
||||
if strings.HasPrefix(file, ".wh.") {
|
||||
originalFile := strings.TrimLeft(file, ".wh.")
|
||||
change.Path = filepath.Join(filepath.Dir(path), originalFile)
|
||||
change.Kind = ChangeDelete
|
||||
} else {
|
||||
// Otherwise, the file was added
|
||||
change.Kind = ChangeAdd
|
||||
|
||||
// ...Unless it already existed in a top layer, in which case, it's a modification
|
||||
for _, layer := range layers {
|
||||
stat, err := os.Stat(filepath.Join(layer, path))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
// The file existed in the top layer, so that's a modification
|
||||
|
||||
// However, if it's a directory, maybe it wasn't actually modified.
|
||||
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
|
||||
if stat.IsDir() && f.IsDir() {
|
||||
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
|
||||
// Both directories are the same, don't record the change
|
||||
return nil
|
||||
}
|
||||
}
|
||||
change.Kind = ChangeModify
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record change
|
||||
changes = append(changes, change)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
// Reset removes all changes to the filesystem, reverting it to its initial state.
|
||||
func (mp *Mountpoint) Reset() error {
|
||||
if err := os.RemoveAll(mp.Rw); err != nil {
|
||||
return err
|
||||
}
|
||||
// We removed the RW directory itself along with its content: let's re-create an empty one.
|
||||
if err := mp.createFolders(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
113
fs/layers.go
113
fs/layers.go
|
@ -1,113 +0,0 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/future"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type LayerStore struct {
|
||||
Root string
|
||||
}
|
||||
|
||||
func NewLayerStore(root string) (*LayerStore, error) {
|
||||
abspath, err := filepath.Abs(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create the root directory if it doesn't exists
|
||||
if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
return &LayerStore{
|
||||
Root: abspath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (store *LayerStore) List() []string {
|
||||
files, err := ioutil.ReadDir(store.Root)
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
var layers []string
|
||||
for _, st := range files {
|
||||
if st.IsDir() {
|
||||
layers = append(layers, path.Join(store.Root, st.Name()))
|
||||
}
|
||||
}
|
||||
return layers
|
||||
}
|
||||
|
||||
func (store *LayerStore) Get(id string) string {
|
||||
if !store.Exists(id) {
|
||||
return ""
|
||||
}
|
||||
return store.layerPath(id)
|
||||
}
|
||||
|
||||
func (store *LayerStore) rootExists() (bool, error) {
|
||||
if stat, err := os.Stat(store.Root); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
} else if !stat.IsDir() {
|
||||
return false, errors.New("Not a directory: " + store.Root)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (store *LayerStore) Init() error {
|
||||
if exists, err := store.rootExists(); err != nil {
|
||||
return err
|
||||
} else if exists {
|
||||
return nil
|
||||
}
|
||||
return os.Mkdir(store.Root, 0700)
|
||||
}
|
||||
|
||||
func (store *LayerStore) Mktemp() (string, error) {
|
||||
tmpName := future.RandomId()
|
||||
tmpPath := path.Join(store.Root, "tmp-"+tmpName)
|
||||
if err := os.Mkdir(tmpPath, 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tmpPath, nil
|
||||
}
|
||||
|
||||
func (store *LayerStore) layerPath(id string) string {
|
||||
return path.Join(store.Root, id)
|
||||
}
|
||||
|
||||
func (store *LayerStore) AddLayer(id string, archive Archive) (string, error) {
|
||||
if _, err := os.Stat(store.layerPath(id)); err == nil {
|
||||
return "", fmt.Errorf("Layer already exists: %v", id)
|
||||
}
|
||||
tmp, err := store.Mktemp()
|
||||
defer os.RemoveAll(tmp)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Mktemp failed: %s", err)
|
||||
}
|
||||
if err := Untar(archive, tmp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
layer := store.layerPath(id)
|
||||
if !store.Exists(id) {
|
||||
if err := os.Rename(tmp, layer); err != nil {
|
||||
return "", fmt.Errorf("Could not rename temp dir to layer %s: %s", layer, err)
|
||||
}
|
||||
}
|
||||
return layer, nil
|
||||
}
|
||||
|
||||
func (store *LayerStore) Exists(id string) bool {
|
||||
st, err := os.Stat(store.layerPath(id))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return st.IsDir()
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func fakeTar() (io.Reader, error) {
|
||||
content := []byte("Hello world!\n")
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
|
||||
hdr := new(tar.Header)
|
||||
hdr.Size = int64(len(content))
|
||||
hdr.Name = name
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tw.Write([]byte(content))
|
||||
}
|
||||
tw.Close()
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func TestLayersInit(t *testing.T) {
|
||||
store := tempStore(t)
|
||||
defer os.RemoveAll(store.Root)
|
||||
// Root should exist
|
||||
if _, err := os.Stat(store.Root); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// List() should be empty
|
||||
if l := store.List(); len(l) != 0 {
|
||||
t.Fatalf("List() should return %d, not %d", 0, len(l))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddLayer(t *testing.T) {
|
||||
store := tempStore(t)
|
||||
defer os.RemoveAll(store.Root)
|
||||
layer, err := store.AddLayer("foo", testArchive(t))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Layer path should exist
|
||||
if _, err := os.Stat(layer); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// List() should return 1 layer
|
||||
if l := store.List(); len(l) != 1 {
|
||||
t.Fatalf("List() should return %d elements, not %d", 1, len(l))
|
||||
}
|
||||
// Get("foo") should return the correct layer
|
||||
if foo := store.Get("foo"); foo != layer {
|
||||
t.Fatalf("get(\"foo\") should return '%d', not '%d'", layer, foo)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddLayerDuplicate(t *testing.T) {
|
||||
store := tempStore(t)
|
||||
defer os.RemoveAll(store.Root)
|
||||
if _, err := store.AddLayer("foobar123", testArchive(t)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := store.AddLayer("foobar123", testArchive(t)); err == nil {
|
||||
t.Fatalf("Creating duplicate layer should fail")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* HELPER FUNCTIONS
|
||||
*/
|
||||
|
||||
func tempStore(t *testing.T) *LayerStore {
|
||||
tmp, err := ioutil.TempDir("", "docker-fs-layerstore-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
store, err := NewLayerStore(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
func testArchive(t *testing.T) Archive {
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return archive
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func countImages(store *Store) int {
|
||||
paths, err := store.Images()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return len(paths)
|
||||
}
|
||||
|
||||
func TestRemoveInPath(t *testing.T) {
|
||||
store, err := TempStore("test-remove-in-path")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 10 create / Delete all
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := store.Create(archive, nil, "foo", "Testing"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
if err := store.RemoveInPath("foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 10 create / Delete 1
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
if err := store.RemoveInPath("foo-0"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 9 {
|
||||
t.Fatalf("Expected 9 images, %d found", c)
|
||||
}
|
||||
|
||||
// Delete failure
|
||||
if err := store.RemoveInPath("Not_Foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 9 {
|
||||
t.Fatalf("Expected 9 images, %d found", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
store, err := TempStore("test-remove")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 1 create / 1 delete
|
||||
img, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 1 {
|
||||
t.Fatalf("Expected 1 images, %d found", c)
|
||||
}
|
||||
if err := store.Remove(img); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 2 create (same name) / 1 delete
|
||||
img1, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img2, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 2 {
|
||||
t.Fatalf("Expected 2 images, %d found", c)
|
||||
}
|
||||
if err := store.Remove(img1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 1 {
|
||||
t.Fatalf("Expected 1 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test delete wrong name
|
||||
// Note: If we change orm and Delete of non existing return error, we will need to change this test
|
||||
if err := store.Remove(&Image{Id: "Not_foo", store: img2.store}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 1 {
|
||||
t.Fatalf("Expected 1 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test delete last one
|
||||
if err := store.Remove(img2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveRegexp(t *testing.T) {
|
||||
store, err := TempStore("test-remove-regexp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 10 create with different names / Delete all good regexp
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
if err := store.RemoveRegexp("foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 10 create with different names / Delete all good regexp globing
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
if err := store.RemoveRegexp("foo-*"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 10 create with different names / Delete all bad regexp
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
if err := store.RemoveRegexp("oo-*"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 10 create with different names / Delete none strict regexp
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
if err := store.RemoveRegexp("^oo-"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test delete 2
|
||||
if err := store.RemoveRegexp("^foo-[1,2]$"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 8 {
|
||||
t.Fatalf("Expected 8 images, %d found", c)
|
||||
}
|
||||
}
|
521
fs/store.go
521
fs/store.go
|
@ -1,521 +0,0 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/future"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/shykes/gorp" //Forked to implement CreateTablesOpts
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
Root string
|
||||
db *sql.DB
|
||||
orm *gorp.DbMap
|
||||
layers *LayerStore
|
||||
}
|
||||
|
||||
type Archive io.Reader
|
||||
|
||||
func New(root string) (*Store, error) {
|
||||
isNewStore := true
|
||||
|
||||
if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
db, err := sql.Open("sqlite3", path.Join(root, "db"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orm := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
|
||||
orm.AddTableWithName(Image{}, "images").SetKeys(false, "Id")
|
||||
orm.AddTableWithName(Path{}, "paths").SetKeys(false, "Path", "Image")
|
||||
orm.AddTableWithName(Mountpoint{}, "mountpoints").SetKeys(false, "Root")
|
||||
orm.AddTableWithName(Tag{}, "tags").SetKeys(false, "TagName")
|
||||
if isNewStore {
|
||||
if err := orm.CreateTablesOpts(true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
layers, err := NewLayerStore(path.Join(root, "layers"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Store{
|
||||
Root: root,
|
||||
db: db,
|
||||
orm: orm,
|
||||
layers: layers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (store *Store) imageList(src []interface{}) []*Image {
|
||||
var images []*Image
|
||||
for _, i := range src {
|
||||
img := i.(*Image)
|
||||
img.store = store
|
||||
images = append(images, img)
|
||||
}
|
||||
return images
|
||||
}
|
||||
|
||||
func (store *Store) Images() ([]*Image, error) {
|
||||
images, err := store.orm.Select(Image{}, "select * from images")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return store.imageList(images), nil
|
||||
}
|
||||
|
||||
func (store *Store) Paths() ([]string, error) {
|
||||
var paths []string
|
||||
rows, err := store.db.Query("select distinct Path from paths order by Path")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
var path string
|
||||
if err := rows.Scan(&path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, path)
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func (store *Store) RemoveInPath(pth string) error {
|
||||
images, err := store.List(pth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, img := range images {
|
||||
if err = store.Remove(img); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteMatch deletes all images whose name matches `pattern`
|
||||
func (store *Store) RemoveRegexp(pattern string) error {
|
||||
// Retrieve all the paths
|
||||
paths, err := store.Paths()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Check the pattern on each elements
|
||||
for _, pth := range paths {
|
||||
if match, err := regexp.MatchString(pattern, pth); err != nil {
|
||||
return err
|
||||
} else if match {
|
||||
// If there is a match, remove it
|
||||
if err := store.RemoveInPath(pth); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) Remove(img *Image) error {
|
||||
_, err := store.orm.Delete(img)
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *Store) List(pth string) ([]*Image, error) {
|
||||
pth = path.Clean(pth)
|
||||
images, err := store.orm.Select(Image{}, "select images.* from images, paths where Path=? and paths.Image=images.Id order by images.Created desc", pth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return store.imageList(images), nil
|
||||
}
|
||||
|
||||
func (store *Store) Find(pth string) (*Image, error) {
|
||||
pth = path.Clean(pth)
|
||||
img, err := store.Get(pth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if img != nil {
|
||||
return img, nil
|
||||
}
|
||||
|
||||
var q string
|
||||
var args []interface{}
|
||||
// FIXME: this breaks if the path contains a ':'
|
||||
// If format is path:rev
|
||||
if parts := strings.SplitN(pth, ":", 2); len(parts) == 2 {
|
||||
q = "select Images.* from images, paths where Path=? and images.Id=? and paths.Image=images.Id"
|
||||
args = []interface{}{parts[0], parts[1]}
|
||||
// If format is path:rev
|
||||
} else {
|
||||
q = "select images.* from images, paths where Path=? and paths.Image=images.Id order by images.Created desc limit 1"
|
||||
args = []interface{}{parts[0]}
|
||||
}
|
||||
images, err := store.orm.Select(Image{}, q, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(images) < 1 {
|
||||
return nil, nil
|
||||
}
|
||||
img = images[0].(*Image)
|
||||
img.store = store
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func (store *Store) Get(id string) (*Image, error) {
|
||||
img, err := store.orm.Get(Image{}, id)
|
||||
if img == nil {
|
||||
return nil, err
|
||||
}
|
||||
res := img.(*Image)
|
||||
res.store = store
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (store *Store) Create(layerData Archive, parent *Image, pth, comment string) (*Image, error) {
|
||||
// FIXME: actually do something with the layer...
|
||||
img := &Image{
|
||||
Id: future.RandomId(),
|
||||
Comment: comment,
|
||||
Created: time.Now().Unix(),
|
||||
store: store,
|
||||
}
|
||||
if parent != nil {
|
||||
img.Parent = parent.Id
|
||||
}
|
||||
// FIXME: Archive should contain compression info. For now we only support uncompressed.
|
||||
err := store.Register(layerData, img, pth)
|
||||
return img, err
|
||||
}
|
||||
|
||||
func (store *Store) Register(layerData Archive, img *Image, pth string) error {
|
||||
img.store = store
|
||||
_, err := store.layers.AddLayer(img.Id, layerData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not add layer: %s", err)
|
||||
}
|
||||
pathObj := &Path{
|
||||
Path: path.Clean(pth),
|
||||
Image: img.Id,
|
||||
}
|
||||
trans, err := store.orm.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not begin transaction: %s", err)
|
||||
}
|
||||
if err := trans.Insert(img); err != nil {
|
||||
return fmt.Errorf("Could not insert image info: %s", err)
|
||||
}
|
||||
if err := trans.Insert(pathObj); err != nil {
|
||||
return fmt.Errorf("Could not insert path info: %s", err)
|
||||
}
|
||||
if err := trans.Commit(); err != nil {
|
||||
return fmt.Errorf("Could not commit transaction: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) Layers() []string {
|
||||
return store.layers.List()
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
Id string
|
||||
Parent string
|
||||
Comment string
|
||||
Created int64
|
||||
store *Store `db:"-"`
|
||||
}
|
||||
|
||||
func (image *Image) Copy(pth string) (*Image, error) {
|
||||
if err := image.store.orm.Insert(&Path{Path: pth, Image: image.Id}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return image, nil
|
||||
}
|
||||
|
||||
type Mountpoint struct {
|
||||
Image string
|
||||
Root string
|
||||
Rw string
|
||||
Store *Store `db:"-"`
|
||||
}
|
||||
|
||||
func (image *Image) Mountpoint(root, rw string) (*Mountpoint, error) {
|
||||
mountpoint := &Mountpoint{
|
||||
Root: path.Clean(root),
|
||||
Rw: path.Clean(rw),
|
||||
Image: image.Id,
|
||||
Store: image.store,
|
||||
}
|
||||
if err := image.store.orm.Insert(mountpoint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mountpoint, nil
|
||||
}
|
||||
|
||||
func (image *Image) layers() ([]string, error) {
|
||||
var list []string
|
||||
var err error
|
||||
currentImg := image
|
||||
for currentImg != nil {
|
||||
if layer := image.store.layers.Get(currentImg.Id); layer != "" {
|
||||
list = append(list, layer)
|
||||
} else {
|
||||
return list, fmt.Errorf("Layer not found for image %s", image.Id)
|
||||
}
|
||||
currentImg, err = currentImg.store.Get(currentImg.Parent)
|
||||
if err != nil {
|
||||
return list, fmt.Errorf("Error while getting parent image: %v", err)
|
||||
}
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return nil, fmt.Errorf("No layer found for image %s\n", image.Id)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (image *Image) Mountpoints() ([]*Mountpoint, error) {
|
||||
var mountpoints []*Mountpoint
|
||||
res, err := image.store.orm.Select(Mountpoint{}, "select * from mountpoints where Image=?", image.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, mp := range res {
|
||||
mountpoints = append(mountpoints, mp.(*Mountpoint))
|
||||
}
|
||||
return mountpoints, nil
|
||||
}
|
||||
|
||||
func (image *Image) Mount(root, rw string) (*Mountpoint, error) {
|
||||
var mountpoint *Mountpoint
|
||||
if mp, err := image.store.FetchMountpoint(root, rw); err != nil {
|
||||
return nil, err
|
||||
} else if mp == nil {
|
||||
mountpoint, err = image.Mountpoint(root, rw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not create mountpoint: %s", err)
|
||||
} else if mountpoint == nil {
|
||||
return nil, fmt.Errorf("No mountpoint created")
|
||||
}
|
||||
} else {
|
||||
mountpoint = mp
|
||||
}
|
||||
|
||||
if err := mountpoint.createFolders(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// FIXME: Now mount the layers
|
||||
rwBranch := fmt.Sprintf("%v=rw", mountpoint.Rw)
|
||||
roBranches := ""
|
||||
layers, err := image.layers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, layer := range layers {
|
||||
roBranches += fmt.Sprintf("%v=ro:", layer)
|
||||
}
|
||||
branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
|
||||
if err := mount("none", mountpoint.Root, "aufs", 0, branches); err != nil {
|
||||
return mountpoint, err
|
||||
}
|
||||
if !mountpoint.Mounted() {
|
||||
return mountpoint, fmt.Errorf("Mount failed")
|
||||
}
|
||||
|
||||
// FIXME: Create tests for deletion
|
||||
// FIXME: move this part to change.go, maybe refactor
|
||||
// fs.Change() to avoid the fake mountpoint
|
||||
// Retrieve the changeset from the parent and apply it to the container
|
||||
// - Retrieve the changes
|
||||
changes, err := image.store.Changes(&Mountpoint{
|
||||
Image: image.Id,
|
||||
Root: layers[0],
|
||||
Rw: layers[0],
|
||||
Store: image.store})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Iterate on changes
|
||||
for _, c := range changes {
|
||||
// If there is a delete
|
||||
if c.Kind == ChangeDelete {
|
||||
// Make sure the directory exists
|
||||
file_path, file_name := path.Dir(c.Path), path.Base(c.Path)
|
||||
if err := os.MkdirAll(path.Join(mountpoint.Rw, file_path), 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// And create the whiteout (we just need to create empty file, discard the return)
|
||||
if _, err := os.Create(path.Join(path.Join(mountpoint.Rw, file_path),
|
||||
".wh."+path.Base(file_name))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return mountpoint, nil
|
||||
}
|
||||
|
||||
func (mp *Mountpoint) EnsureMounted() error {
|
||||
if mp.Mounted() {
|
||||
return nil
|
||||
}
|
||||
img, err := mp.Store.Get(mp.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = img.Mount(mp.Root, mp.Rw)
|
||||
return err
|
||||
}
|
||||
|
||||
func (mp *Mountpoint) createFolders() error {
|
||||
if err := os.Mkdir(mp.Root, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
if err := os.Mkdir(mp.Rw, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mp *Mountpoint) Mounted() bool {
|
||||
root, err := os.Stat(mp.Root)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
parent, err := os.Stat(filepath.Join(mp.Root, ".."))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rootSt := root.Sys().(*syscall.Stat_t)
|
||||
parentSt := parent.Sys().(*syscall.Stat_t)
|
||||
return rootSt.Dev != parentSt.Dev
|
||||
}
|
||||
|
||||
func (mp *Mountpoint) Umount() error {
|
||||
if !mp.Mounted() {
|
||||
return fmt.Errorf("Mountpoint doesn't seem to be mounted")
|
||||
}
|
||||
if err := syscall.Unmount(mp.Root, 0); err != nil {
|
||||
return fmt.Errorf("Unmount syscall failed: %v", err)
|
||||
}
|
||||
if mp.Mounted() {
|
||||
return fmt.Errorf("Umount: Filesystem still mounted after calling umount(%v)", mp.Root)
|
||||
}
|
||||
// Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint
|
||||
// for some time. We'll just keep retrying until it succeeds.
|
||||
for retries := 0; retries < 1000; retries++ {
|
||||
err := os.Remove(mp.Root)
|
||||
if err == nil {
|
||||
// rm mntpoint succeeded
|
||||
return nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
// mntpoint doesn't exist anymore. Success.
|
||||
return nil
|
||||
}
|
||||
// fmt.Printf("(%v) Remove %v returned: %v\n", retries, mp.Root, err)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("Umount: Failed to umount %v", mp.Root)
|
||||
|
||||
}
|
||||
|
||||
func (mp *Mountpoint) Deregister() error {
|
||||
if mp.Mounted() {
|
||||
return fmt.Errorf("Mountpoint is currently mounted, can't deregister")
|
||||
}
|
||||
|
||||
_, err := mp.Store.orm.Delete(mp)
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *Store) FetchMountpoint(root, rw string) (*Mountpoint, error) {
|
||||
res, err := store.orm.Select(Mountpoint{}, "select * from mountpoints where Root=? and Rw=?", root, rw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(res) < 1 || res[0] == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mp := res[0].(*Mountpoint)
|
||||
mp.Store = store
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
// OpenFile opens the named file for reading.
|
||||
func (mp *Mountpoint) OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
if err := mp.EnsureMounted(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.OpenFile(filepath.Join(mp.Root, path), flag, perm)
|
||||
}
|
||||
|
||||
// ReadDir reads the directory named by dirname, relative to the Mountpoint's root,
|
||||
// and returns a list of sorted directory entries
|
||||
func (mp *Mountpoint) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||
if err := mp.EnsureMounted(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutil.ReadDir(filepath.Join(mp.Root, dirname))
|
||||
}
|
||||
|
||||
func (store *Store) AddTag(imageId, tagName string) error {
|
||||
if image, err := store.Get(imageId); err != nil {
|
||||
return err
|
||||
} else if image == nil {
|
||||
return fmt.Errorf("No image with ID %s", imageId)
|
||||
}
|
||||
|
||||
err2 := store.orm.Insert(&Tag{
|
||||
TagName: tagName,
|
||||
Image: imageId,
|
||||
})
|
||||
|
||||
return err2
|
||||
}
|
||||
|
||||
func (store *Store) GetByTag(tagName string) (*Image, error) {
|
||||
res, err := store.orm.Get(Tag{}, tagName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if res == nil {
|
||||
return nil, fmt.Errorf("No image associated to tag \"%s\"", tagName)
|
||||
}
|
||||
|
||||
tag := res.(*Tag)
|
||||
|
||||
img, err2 := store.Get(tag.Image)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
} else if img == nil {
|
||||
return nil, fmt.Errorf("Tag was found but image seems to be inexistent.")
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
type Path struct {
|
||||
Path string
|
||||
Image string
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
TagName string
|
||||
Image string
|
||||
}
|
277
fs/store_test.go
277
fs/store_test.go
|
@ -1,277 +0,0 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/future"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
store, err := TempStore("testinit")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
paths, err := store.Paths()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if l := len(paths); l != 0 {
|
||||
t.Fatal("Fresh store should be empty after init (len=%d)", l)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Do more extensive tests (ex: create multiple, delete, recreate;
|
||||
// create multiple, check the amount of images and paths, etc..)
|
||||
func TestCreate(t *testing.T) {
|
||||
store, err := TempStore("testcreate")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if images, err := store.Images(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
if images, err := store.List("foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Path foo has wrong number of images (should be %d, not %d)", 1, l)
|
||||
} else if images[0].Id != image.Id {
|
||||
t.Fatalf("Imported image should be listed at path foo (%s != %s)", images[0], image)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
store, err := TempStore("testregister")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image := &Image{
|
||||
Id: future.RandomId(),
|
||||
Comment: "testing",
|
||||
Created: time.Now().Unix(),
|
||||
store: store,
|
||||
}
|
||||
err = store.Register(archive, image, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if images, err := store.Images(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
if images, err := store.List("foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Path foo has wrong number of images (should be %d, not %d)", 1, l)
|
||||
} else if images[0].Id != image.Id {
|
||||
t.Fatalf("Imported image should be listed at path foo (%s != %s)", images[0], image)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTag(t *testing.T) {
|
||||
store, err := TempStore("testtag")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if images, err := store.Images(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
|
||||
if err := store.AddTag(image.Id, "baz"); err != nil {
|
||||
t.Fatalf("Error while adding a tag to created image: %s", err)
|
||||
}
|
||||
|
||||
if taggedImage, err := store.GetByTag("baz"); err != nil {
|
||||
t.Fatalf("Error while trying to retrieve image for tag 'baz': %s", err)
|
||||
} else if taggedImage.Id != image.Id {
|
||||
t.Fatalf("Expected to retrieve image %s but found %s instead", image.Id, taggedImage.Id)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy an image to a new path
|
||||
func TestCopyNewPath(t *testing.T) {
|
||||
store, err := TempStore("testcopynewpath")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dst, err := src.Copy("bar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// ID should be the same
|
||||
if src.Id != dst.Id {
|
||||
t.Fatal("Different IDs")
|
||||
}
|
||||
// Check number of images at source path
|
||||
if images, err := store.List("foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatal("Wrong number of images at source path (should be %d, not %d)", 1, l)
|
||||
}
|
||||
// Check number of images at destination path
|
||||
if images, err := store.List("bar"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatal("Wrong number of images at destination path (should be %d, not %d)", 1, l)
|
||||
}
|
||||
if err := healthCheck(store); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Copying an image to the same path twice should fail
|
||||
func TestCopySameName(t *testing.T) {
|
||||
store, err := TempStore("testcopysamename")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = src.Copy("foo")
|
||||
if err == nil {
|
||||
t.Fatal("Copying an image to the same patch twice should fail.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountPoint(t *testing.T) {
|
||||
store, err := TempStore("test-mountpoint")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mountpoint, err := image.Mountpoint("/tmp/a", "/tmp/b")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mountpoint.Root != "/tmp/a" {
|
||||
t.Fatal("Wrong mountpoint root (should be %s, not %s)", "/tmp/a", mountpoint.Root)
|
||||
}
|
||||
if mountpoint.Rw != "/tmp/b" {
|
||||
t.Fatal("Wrong mountpoint root (should be %s, not %s)", "/tmp/b", mountpoint.Rw)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountpointDuplicateRoot(t *testing.T) {
|
||||
store, err := TempStore("test-mountpoint")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = image.Mountpoint("/tmp/a", "/tmp/b")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = image.Mountpoint("/tmp/a", "/tmp/foobar"); err == nil {
|
||||
t.Fatal("Duplicate mountpoint root should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TempStore(prefix string) (*Store, error) {
|
||||
dir, err := ioutil.TempDir("", "docker-fs-test-"+prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return New(dir)
|
||||
}
|
||||
|
||||
func nuke(store *Store) error {
|
||||
return os.RemoveAll(store.Root)
|
||||
}
|
||||
|
||||
// Look for inconsistencies in a store.
|
||||
func healthCheck(store *Store) error {
|
||||
parents := make(map[string]bool)
|
||||
paths, err := store.Paths()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, path := range paths {
|
||||
images, err := store.List(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
IDs := make(map[string]bool) // All IDs for this path
|
||||
for _, img := range images {
|
||||
// Check for duplicate IDs per path
|
||||
if _, exists := IDs[img.Id]; exists {
|
||||
return fmt.Errorf("Duplicate ID: %s", img.Id)
|
||||
} else {
|
||||
IDs[img.Id] = true
|
||||
}
|
||||
// Store parent for 2nd pass
|
||||
if parent := img.Parent; parent != "" {
|
||||
parents[parent] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check non-existing parents
|
||||
for parent := range parents {
|
||||
if _, exists := parents[parent]; !exists {
|
||||
return fmt.Errorf("Reference to non-registered parent: %s", parent)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
Загрузка…
Ссылка в новой задаче