| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,75 @@ |
| 0 |
+package docker |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "errors" |
|
| 4 |
+ "io" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "os/exec" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+type Archive io.Reader |
|
| 10 |
+ |
|
| 11 |
+type Compression uint32 |
|
| 12 |
+ |
|
| 13 |
+const ( |
|
| 14 |
+ Uncompressed Compression = iota |
|
| 15 |
+ Bzip2 |
|
| 16 |
+ Gzip |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+func (compression *Compression) Flag() string {
|
|
| 20 |
+ switch *compression {
|
|
| 21 |
+ case Bzip2: |
|
| 22 |
+ return "j" |
|
| 23 |
+ case Gzip: |
|
| 24 |
+ return "z" |
|
| 25 |
+ } |
|
| 26 |
+ return "" |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+func Tar(path string, compression Compression) (io.Reader, error) {
|
|
| 30 |
+ cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".")
|
|
| 31 |
+ return CmdStream(cmd) |
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+func Untar(archive io.Reader, path string) error {
|
|
| 35 |
+ cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
|
|
| 36 |
+ cmd.Stdin = archive |
|
| 37 |
+ output, err := cmd.CombinedOutput() |
|
| 38 |
+ if err != nil {
|
|
| 39 |
+ return errors.New(err.Error() + ": " + string(output)) |
|
| 40 |
+ } |
|
| 41 |
+ return nil |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
|
|
| 45 |
+ stdout, err := cmd.StdoutPipe() |
|
| 46 |
+ if err != nil {
|
|
| 47 |
+ return nil, err |
|
| 48 |
+ } |
|
| 49 |
+ stderr, err := cmd.StderrPipe() |
|
| 50 |
+ if err != nil {
|
|
| 51 |
+ return nil, err |
|
| 52 |
+ } |
|
| 53 |
+ pipeR, pipeW := io.Pipe() |
|
| 54 |
+ go func() {
|
|
| 55 |
+ _, err := io.Copy(pipeW, stdout) |
|
| 56 |
+ if err != nil {
|
|
| 57 |
+ pipeW.CloseWithError(err) |
|
| 58 |
+ } |
|
| 59 |
+ errText, e := ioutil.ReadAll(stderr) |
|
| 60 |
+ if e != nil {
|
|
| 61 |
+ errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
|
|
| 62 |
+ } |
|
| 63 |
+ if err := cmd.Wait(); err != nil {
|
|
| 64 |
+ // FIXME: can this block if stderr outputs more than the size of StderrPipe()'s buffer? |
|
| 65 |
+ pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText))) |
|
| 66 |
+ } else {
|
|
| 67 |
+ pipeW.Close() |
|
| 68 |
+ } |
|
| 69 |
+ }() |
|
| 70 |
+ if err := cmd.Start(); err != nil {
|
|
| 71 |
+ return nil, err |
|
| 72 |
+ } |
|
| 73 |
+ return pipeR, nil |
|
| 74 |
+} |
| 0 | 75 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,54 @@ |
| 0 |
+package docker |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io/ioutil" |
|
| 4 |
+ "os" |
|
| 5 |
+ "os/exec" |
|
| 6 |
+ "testing" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func TestCmdStreamBad(t *testing.T) {
|
|
| 10 |
+ badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
|
|
| 11 |
+ out, err := CmdStream(badCmd) |
|
| 12 |
+ if err != nil {
|
|
| 13 |
+ t.Fatalf("Failed to start command: " + err.Error())
|
|
| 14 |
+ } |
|
| 15 |
+ if output, err := ioutil.ReadAll(out); err == nil {
|
|
| 16 |
+ t.Fatalf("Command should have failed")
|
|
| 17 |
+ } else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
|
|
| 18 |
+ t.Fatalf("Wrong error value (%s)", err.Error())
|
|
| 19 |
+ } else if s := string(output); s != "hello\n" {
|
|
| 20 |
+ t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
|
|
| 21 |
+ } |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+func TestCmdStreamGood(t *testing.T) {
|
|
| 25 |
+ cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
|
|
| 26 |
+ out, err := CmdStream(cmd) |
|
| 27 |
+ if err != nil {
|
|
| 28 |
+ t.Fatal(err) |
|
| 29 |
+ } |
|
| 30 |
+ if output, err := ioutil.ReadAll(out); err != nil {
|
|
| 31 |
+ t.Fatalf("Command should not have failed (err=%s)", err)
|
|
| 32 |
+ } else if s := string(output); s != "hello\n" {
|
|
| 33 |
+ t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
|
|
| 34 |
+ } |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+func TestTarUntar(t *testing.T) {
|
|
| 38 |
+ archive, err := Tar(".", Uncompressed)
|
|
| 39 |
+ if err != nil {
|
|
| 40 |
+ t.Fatal(err) |
|
| 41 |
+ } |
|
| 42 |
+ tmp, err := ioutil.TempDir("", "docker-test-untar")
|
|
| 43 |
+ if err != nil {
|
|
| 44 |
+ t.Fatal(err) |
|
| 45 |
+ } |
|
| 46 |
+ defer os.RemoveAll(tmp) |
|
| 47 |
+ if err := Untar(archive, tmp); err != nil {
|
|
| 48 |
+ t.Fatal(err) |
|
| 49 |
+ } |
|
| 50 |
+ if _, err := os.Stat(tmp); err != nil {
|
|
| 51 |
+ t.Fatalf("Error stating %s: %s", tmp, err.Error())
|
|
| 52 |
+ } |
|
| 53 |
+} |
| 0 | 54 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,106 @@ |
| 0 |
+package docker |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "os" |
|
| 5 |
+ "path/filepath" |
|
| 6 |
+ "strings" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+type ChangeType int |
|
| 10 |
+ |
|
| 11 |
+const ( |
|
| 12 |
+ ChangeModify = iota |
|
| 13 |
+ ChangeAdd |
|
| 14 |
+ ChangeDelete |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+type Change struct {
|
|
| 18 |
+ Path string |
|
| 19 |
+ Kind ChangeType |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+func (change *Change) String() string {
|
|
| 23 |
+ var kind string |
|
| 24 |
+ switch change.Kind {
|
|
| 25 |
+ case ChangeModify: |
|
| 26 |
+ kind = "C" |
|
| 27 |
+ case ChangeAdd: |
|
| 28 |
+ kind = "A" |
|
| 29 |
+ case ChangeDelete: |
|
| 30 |
+ kind = "D" |
|
| 31 |
+ } |
|
| 32 |
+ return fmt.Sprintf("%s %s", kind, change.Path)
|
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+func Changes(layers []string, rw string) ([]Change, error) {
|
|
| 36 |
+ var changes []Change |
|
| 37 |
+ err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
|
|
| 38 |
+ if err != nil {
|
|
| 39 |
+ return err |
|
| 40 |
+ } |
|
| 41 |
+ |
|
| 42 |
+ // Rebase path |
|
| 43 |
+ path, err = filepath.Rel(rw, path) |
|
| 44 |
+ if err != nil {
|
|
| 45 |
+ return err |
|
| 46 |
+ } |
|
| 47 |
+ path = filepath.Join("/", path)
|
|
| 48 |
+ |
|
| 49 |
+ // Skip root |
|
| 50 |
+ if path == "/" {
|
|
| 51 |
+ return nil |
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ // Skip AUFS metadata |
|
| 55 |
+ if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
|
|
| 56 |
+ return err |
|
| 57 |
+ } |
|
| 58 |
+ |
|
| 59 |
+ change := Change{
|
|
| 60 |
+ Path: path, |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ // Find out what kind of modification happened |
|
| 64 |
+ file := filepath.Base(path) |
|
| 65 |
+ // If there is a whiteout, then the file was removed |
|
| 66 |
+ if strings.HasPrefix(file, ".wh.") {
|
|
| 67 |
+ originalFile := strings.TrimLeft(file, ".wh.") |
|
| 68 |
+ change.Path = filepath.Join(filepath.Dir(path), originalFile) |
|
| 69 |
+ change.Kind = ChangeDelete |
|
| 70 |
+ } else {
|
|
| 71 |
+ // Otherwise, the file was added |
|
| 72 |
+ change.Kind = ChangeAdd |
|
| 73 |
+ |
|
| 74 |
+ // ...Unless it already existed in a top layer, in which case, it's a modification |
|
| 75 |
+ for _, layer := range layers {
|
|
| 76 |
+ stat, err := os.Stat(filepath.Join(layer, path)) |
|
| 77 |
+ if err != nil && !os.IsNotExist(err) {
|
|
| 78 |
+ return err |
|
| 79 |
+ } |
|
| 80 |
+ if err == nil {
|
|
| 81 |
+ // The file existed in the top layer, so that's a modification |
|
| 82 |
+ |
|
| 83 |
+ // However, if it's a directory, maybe it wasn't actually modified. |
|
| 84 |
+ // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar |
|
| 85 |
+ if stat.IsDir() && f.IsDir() {
|
|
| 86 |
+ if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
|
|
| 87 |
+ // Both directories are the same, don't record the change |
|
| 88 |
+ return nil |
|
| 89 |
+ } |
|
| 90 |
+ } |
|
| 91 |
+ change.Kind = ChangeModify |
|
| 92 |
+ break |
|
| 93 |
+ } |
|
| 94 |
+ } |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ // Record change |
|
| 98 |
+ changes = append(changes, change) |
|
| 99 |
+ return nil |
|
| 100 |
+ }) |
|
| 101 |
+ if err != nil {
|
|
| 102 |
+ return nil, err |
|
| 103 |
+ } |
|
| 104 |
+ return changes, nil |
|
| 105 |
+} |
| ... | ... |
@@ -4,7 +4,6 @@ import ( |
| 4 | 4 |
"encoding/json" |
| 5 | 5 |
"errors" |
| 6 | 6 |
"fmt" |
| 7 |
- "github.com/dotcloud/docker/graph" |
|
| 8 | 7 |
"github.com/kr/pty" |
| 9 | 8 |
"io" |
| 10 | 9 |
"io/ioutil" |
| ... | ... |
@@ -63,11 +62,6 @@ type NetworkSettings struct {
|
| 63 | 63 |
PortMapping map[string]string |
| 64 | 64 |
} |
| 65 | 65 |
|
| 66 |
-func GenerateId() string {
|
|
| 67 |
- return graph.GenerateId() // Re-use the same code to generate container and image IDs |
|
| 68 |
- // (this might change when image Ids become content-based) |
|
| 69 |
-} |
|
| 70 |
- |
|
| 71 | 66 |
func (container *Container) Cmd() *exec.Cmd {
|
| 72 | 67 |
return container.cmd |
| 73 | 68 |
} |
| ... | ... |
@@ -376,15 +370,15 @@ func (container *Container) Wait() int {
|
| 376 | 376 |
return container.State.ExitCode |
| 377 | 377 |
} |
| 378 | 378 |
|
| 379 |
-func (container *Container) ExportRw() (graph.Archive, error) {
|
|
| 380 |
- return graph.Tar(container.rwPath(), graph.Uncompressed) |
|
| 379 |
+func (container *Container) ExportRw() (Archive, error) {
|
|
| 380 |
+ return Tar(container.rwPath(), Uncompressed) |
|
| 381 | 381 |
} |
| 382 | 382 |
|
| 383 |
-func (container *Container) Export() (graph.Archive, error) {
|
|
| 383 |
+func (container *Container) Export() (Archive, error) {
|
|
| 384 | 384 |
if err := container.EnsureMounted(); err != nil {
|
| 385 | 385 |
return nil, err |
| 386 | 386 |
} |
| 387 |
- return graph.Tar(container.RootfsPath(), graph.Uncompressed) |
|
| 387 |
+ return Tar(container.RootfsPath(), Uncompressed) |
|
| 388 | 388 |
} |
| 389 | 389 |
|
| 390 | 390 |
func (container *Container) WaitTimeout(timeout time.Duration) error {
|
| ... | ... |
@@ -420,7 +414,7 @@ func (container *Container) Mount() error {
|
| 420 | 420 |
return image.Mount(container.RootfsPath(), container.rwPath()) |
| 421 | 421 |
} |
| 422 | 422 |
|
| 423 |
-func (container *Container) Changes() ([]graph.Change, error) {
|
|
| 423 |
+func (container *Container) Changes() ([]Change, error) {
|
|
| 424 | 424 |
image, err := container.GetImage() |
| 425 | 425 |
if err != nil {
|
| 426 | 426 |
return nil, err |
| ... | ... |
@@ -428,7 +422,7 @@ func (container *Container) Changes() ([]graph.Change, error) {
|
| 428 | 428 |
return image.Changes(container.rwPath()) |
| 429 | 429 |
} |
| 430 | 430 |
|
| 431 |
-func (container *Container) GetImage() (*graph.Image, error) {
|
|
| 431 |
+func (container *Container) GetImage() (*Image, error) {
|
|
| 432 | 432 |
if container.runtime == nil {
|
| 433 | 433 |
return nil, fmt.Errorf("Can't get image of unregistered container")
|
| 434 | 434 |
} |
| ... | ... |
@@ -436,11 +430,11 @@ func (container *Container) GetImage() (*graph.Image, error) {
|
| 436 | 436 |
} |
| 437 | 437 |
|
| 438 | 438 |
func (container *Container) Mounted() (bool, error) {
|
| 439 |
- return graph.Mounted(container.RootfsPath()) |
|
| 439 |
+ return Mounted(container.RootfsPath()) |
|
| 440 | 440 |
} |
| 441 | 441 |
|
| 442 | 442 |
func (container *Container) Unmount() error {
|
| 443 |
- return graph.Unmount(container.RootfsPath()) |
|
| 443 |
+ return Unmount(container.RootfsPath()) |
|
| 444 | 444 |
} |
| 445 | 445 |
|
| 446 | 446 |
func (container *Container) logPath(name string) string {
|
| 447 | 447 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,157 @@ |
| 0 |
+package docker |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "os" |
|
| 6 |
+ "path" |
|
| 7 |
+ "path/filepath" |
|
| 8 |
+ "time" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+type Graph struct {
|
|
| 12 |
+ Root string |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+func NewGraph(root string) (*Graph, error) {
|
|
| 16 |
+ abspath, err := filepath.Abs(root) |
|
| 17 |
+ if err != nil {
|
|
| 18 |
+ return nil, err |
|
| 19 |
+ } |
|
| 20 |
+ // Create the root directory if it doesn't exists |
|
| 21 |
+ if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
|
|
| 22 |
+ return nil, err |
|
| 23 |
+ } |
|
| 24 |
+ return &Graph{
|
|
| 25 |
+ Root: abspath, |
|
| 26 |
+ }, nil |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+func (graph *Graph) Exists(id string) bool {
|
|
| 30 |
+ if _, err := graph.Get(id); err != nil {
|
|
| 31 |
+ return false |
|
| 32 |
+ } |
|
| 33 |
+ return true |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+func (graph *Graph) Get(id string) (*Image, error) {
|
|
| 37 |
+ img, err := LoadImage(graph.imageRoot(id)) |
|
| 38 |
+ if err != nil {
|
|
| 39 |
+ return nil, err |
|
| 40 |
+ } |
|
| 41 |
+ if img.Id != id {
|
|
| 42 |
+ return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.Id)
|
|
| 43 |
+ } |
|
| 44 |
+ img.graph = graph |
|
| 45 |
+ return img, nil |
|
| 46 |
+} |
|
| 47 |
+ |
|
| 48 |
+func (graph *Graph) Create(layerData Archive, parent, comment string) (*Image, error) {
|
|
| 49 |
+ img := &Image{
|
|
| 50 |
+ Id: GenerateId(), |
|
| 51 |
+ Parent: parent, |
|
| 52 |
+ Comment: comment, |
|
| 53 |
+ Created: time.Now(), |
|
| 54 |
+ } |
|
| 55 |
+ if err := graph.Register(layerData, img); err != nil {
|
|
| 56 |
+ return nil, err |
|
| 57 |
+ } |
|
| 58 |
+ return img, nil |
|
| 59 |
+} |
|
| 60 |
+ |
|
| 61 |
+func (graph *Graph) Register(layerData Archive, img *Image) error {
|
|
| 62 |
+ if err := ValidateId(img.Id); err != nil {
|
|
| 63 |
+ return err |
|
| 64 |
+ } |
|
| 65 |
+ // (This is a convenience to save time. Race conditions are taken care of by os.Rename) |
|
| 66 |
+ if graph.Exists(img.Id) {
|
|
| 67 |
+ return fmt.Errorf("Image %s already exists", img.Id)
|
|
| 68 |
+ } |
|
| 69 |
+ tmp, err := graph.Mktemp(img.Id) |
|
| 70 |
+ defer os.RemoveAll(tmp) |
|
| 71 |
+ if err != nil {
|
|
| 72 |
+ return fmt.Errorf("Mktemp failed: %s", err)
|
|
| 73 |
+ } |
|
| 74 |
+ if err := StoreImage(img, layerData, tmp); err != nil {
|
|
| 75 |
+ return err |
|
| 76 |
+ } |
|
| 77 |
+ // Commit |
|
| 78 |
+ if err := os.Rename(tmp, graph.imageRoot(img.Id)); err != nil {
|
|
| 79 |
+ return err |
|
| 80 |
+ } |
|
| 81 |
+ img.graph = graph |
|
| 82 |
+ return nil |
|
| 83 |
+} |
|
| 84 |
+ |
|
| 85 |
+func (graph *Graph) Mktemp(id string) (string, error) {
|
|
| 86 |
+ tmp, err := NewGraph(path.Join(graph.Root, ":tmp:")) |
|
| 87 |
+ if err != nil {
|
|
| 88 |
+ return "", fmt.Errorf("Couldn't create temp: %s", err)
|
|
| 89 |
+ } |
|
| 90 |
+ if tmp.Exists(id) {
|
|
| 91 |
+ return "", fmt.Errorf("Image %d already exists", id)
|
|
| 92 |
+ } |
|
| 93 |
+ return tmp.imageRoot(id), nil |
|
| 94 |
+} |
|
| 95 |
+ |
|
| 96 |
+func (graph *Graph) Garbage() (*Graph, error) {
|
|
| 97 |
+ return NewGraph(path.Join(graph.Root, ":garbage:")) |
|
| 98 |
+} |
|
| 99 |
+ |
|
| 100 |
+func (graph *Graph) Delete(id string) error {
|
|
| 101 |
+ garbage, err := graph.Garbage() |
|
| 102 |
+ if err != nil {
|
|
| 103 |
+ return err |
|
| 104 |
+ } |
|
| 105 |
+ return os.Rename(graph.imageRoot(id), garbage.imageRoot(id)) |
|
| 106 |
+} |
|
| 107 |
+ |
|
| 108 |
+func (graph *Graph) Undelete(id string) error {
|
|
| 109 |
+ garbage, err := graph.Garbage() |
|
| 110 |
+ if err != nil {
|
|
| 111 |
+ return err |
|
| 112 |
+ } |
|
| 113 |
+ return os.Rename(garbage.imageRoot(id), graph.imageRoot(id)) |
|
| 114 |
+} |
|
| 115 |
+ |
|
| 116 |
+func (graph *Graph) GarbageCollect() error {
|
|
| 117 |
+ garbage, err := graph.Garbage() |
|
| 118 |
+ if err != nil {
|
|
| 119 |
+ return err |
|
| 120 |
+ } |
|
| 121 |
+ return os.RemoveAll(garbage.Root) |
|
| 122 |
+} |
|
| 123 |
+ |
|
| 124 |
+func (graph *Graph) Map() (map[string]*Image, error) {
|
|
| 125 |
+ // FIXME: this should replace All() |
|
| 126 |
+ all, err := graph.All() |
|
| 127 |
+ if err != nil {
|
|
| 128 |
+ return nil, err |
|
| 129 |
+ } |
|
| 130 |
+ images := make(map[string]*Image, len(all)) |
|
| 131 |
+ for _, image := range all {
|
|
| 132 |
+ images[image.Id] = image |
|
| 133 |
+ } |
|
| 134 |
+ return images, nil |
|
| 135 |
+} |
|
| 136 |
+ |
|
| 137 |
+func (graph *Graph) All() ([]*Image, error) {
|
|
| 138 |
+ files, err := ioutil.ReadDir(graph.Root) |
|
| 139 |
+ if err != nil {
|
|
| 140 |
+ return nil, err |
|
| 141 |
+ } |
|
| 142 |
+ var images []*Image |
|
| 143 |
+ for _, st := range files {
|
|
| 144 |
+ if img, err := graph.Get(st.Name()); err != nil {
|
|
| 145 |
+ // Skip image |
|
| 146 |
+ continue |
|
| 147 |
+ } else {
|
|
| 148 |
+ images = append(images, img) |
|
| 149 |
+ } |
|
| 150 |
+ } |
|
| 151 |
+ return images, nil |
|
| 152 |
+} |
|
| 153 |
+ |
|
| 154 |
+func (graph *Graph) imageRoot(id string) string {
|
|
| 155 |
+ return path.Join(graph.Root, id) |
|
| 156 |
+} |
| 0 | 157 |
deleted file mode 100644 |
| ... | ... |
@@ -1,75 +0,0 @@ |
| 1 |
-package graph |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "errors" |
|
| 5 |
- "io" |
|
| 6 |
- "io/ioutil" |
|
| 7 |
- "os/exec" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-type Archive io.Reader |
|
| 11 |
- |
|
| 12 |
-type Compression uint32 |
|
| 13 |
- |
|
| 14 |
-const ( |
|
| 15 |
- Uncompressed Compression = iota |
|
| 16 |
- Bzip2 |
|
| 17 |
- Gzip |
|
| 18 |
-) |
|
| 19 |
- |
|
| 20 |
-func (compression *Compression) Flag() string {
|
|
| 21 |
- switch *compression {
|
|
| 22 |
- case Bzip2: |
|
| 23 |
- return "j" |
|
| 24 |
- case Gzip: |
|
| 25 |
- return "z" |
|
| 26 |
- } |
|
| 27 |
- return "" |
|
| 28 |
-} |
|
| 29 |
- |
|
| 30 |
-func Tar(path string, compression Compression) (io.Reader, error) {
|
|
| 31 |
- cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".")
|
|
| 32 |
- return CmdStream(cmd) |
|
| 33 |
-} |
|
| 34 |
- |
|
| 35 |
-func Untar(archive io.Reader, path string) error {
|
|
| 36 |
- cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
|
|
| 37 |
- cmd.Stdin = archive |
|
| 38 |
- output, err := cmd.CombinedOutput() |
|
| 39 |
- if err != nil {
|
|
| 40 |
- return errors.New(err.Error() + ": " + string(output)) |
|
| 41 |
- } |
|
| 42 |
- return nil |
|
| 43 |
-} |
|
| 44 |
- |
|
| 45 |
-func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
|
|
| 46 |
- stdout, err := cmd.StdoutPipe() |
|
| 47 |
- if err != nil {
|
|
| 48 |
- return nil, err |
|
| 49 |
- } |
|
| 50 |
- stderr, err := cmd.StderrPipe() |
|
| 51 |
- if err != nil {
|
|
| 52 |
- return nil, err |
|
| 53 |
- } |
|
| 54 |
- pipeR, pipeW := io.Pipe() |
|
| 55 |
- go func() {
|
|
| 56 |
- _, err := io.Copy(pipeW, stdout) |
|
| 57 |
- if err != nil {
|
|
| 58 |
- pipeW.CloseWithError(err) |
|
| 59 |
- } |
|
| 60 |
- errText, e := ioutil.ReadAll(stderr) |
|
| 61 |
- if e != nil {
|
|
| 62 |
- errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
|
|
| 63 |
- } |
|
| 64 |
- if err := cmd.Wait(); err != nil {
|
|
| 65 |
- // FIXME: can this block if stderr outputs more than the size of StderrPipe()'s buffer? |
|
| 66 |
- pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText))) |
|
| 67 |
- } else {
|
|
| 68 |
- pipeW.Close() |
|
| 69 |
- } |
|
| 70 |
- }() |
|
| 71 |
- if err := cmd.Start(); err != nil {
|
|
| 72 |
- return nil, err |
|
| 73 |
- } |
|
| 74 |
- return pipeR, nil |
|
| 75 |
-} |
| 76 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,54 +0,0 @@ |
| 1 |
-package graph |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "io/ioutil" |
|
| 5 |
- "os" |
|
| 6 |
- "os/exec" |
|
| 7 |
- "testing" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-func TestCmdStreamBad(t *testing.T) {
|
|
| 11 |
- badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
|
|
| 12 |
- out, err := CmdStream(badCmd) |
|
| 13 |
- if err != nil {
|
|
| 14 |
- t.Fatalf("Failed to start command: " + err.Error())
|
|
| 15 |
- } |
|
| 16 |
- if output, err := ioutil.ReadAll(out); err == nil {
|
|
| 17 |
- t.Fatalf("Command should have failed")
|
|
| 18 |
- } else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
|
|
| 19 |
- t.Fatalf("Wrong error value (%s)", err.Error())
|
|
| 20 |
- } else if s := string(output); s != "hello\n" {
|
|
| 21 |
- t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
|
|
| 22 |
- } |
|
| 23 |
-} |
|
| 24 |
- |
|
| 25 |
-func TestCmdStreamGood(t *testing.T) {
|
|
| 26 |
- cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
|
|
| 27 |
- out, err := CmdStream(cmd) |
|
| 28 |
- if err != nil {
|
|
| 29 |
- t.Fatal(err) |
|
| 30 |
- } |
|
| 31 |
- if output, err := ioutil.ReadAll(out); err != nil {
|
|
| 32 |
- t.Fatalf("Command should not have failed (err=%s)", err)
|
|
| 33 |
- } else if s := string(output); s != "hello\n" {
|
|
| 34 |
- t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
|
|
| 35 |
- } |
|
| 36 |
-} |
|
| 37 |
- |
|
| 38 |
-func TestTarUntar(t *testing.T) {
|
|
| 39 |
- archive, err := Tar(".", Uncompressed)
|
|
| 40 |
- if err != nil {
|
|
| 41 |
- t.Fatal(err) |
|
| 42 |
- } |
|
| 43 |
- tmp, err := ioutil.TempDir("", "docker-test-untar")
|
|
| 44 |
- if err != nil {
|
|
| 45 |
- t.Fatal(err) |
|
| 46 |
- } |
|
| 47 |
- defer os.RemoveAll(tmp) |
|
| 48 |
- if err := Untar(archive, tmp); err != nil {
|
|
| 49 |
- t.Fatal(err) |
|
| 50 |
- } |
|
| 51 |
- if _, err := os.Stat(tmp); err != nil {
|
|
| 52 |
- t.Fatalf("Error stating %s: %s", tmp, err.Error())
|
|
| 53 |
- } |
|
| 54 |
-} |
| 55 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,106 +0,0 @@ |
| 1 |
-package graph |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "os" |
|
| 6 |
- "path/filepath" |
|
| 7 |
- "strings" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-type ChangeType int |
|
| 11 |
- |
|
| 12 |
-const ( |
|
| 13 |
- ChangeModify = iota |
|
| 14 |
- ChangeAdd |
|
| 15 |
- ChangeDelete |
|
| 16 |
-) |
|
| 17 |
- |
|
| 18 |
-type Change struct {
|
|
| 19 |
- Path string |
|
| 20 |
- Kind ChangeType |
|
| 21 |
-} |
|
| 22 |
- |
|
| 23 |
-func (change *Change) String() string {
|
|
| 24 |
- var kind string |
|
| 25 |
- switch change.Kind {
|
|
| 26 |
- case ChangeModify: |
|
| 27 |
- kind = "C" |
|
| 28 |
- case ChangeAdd: |
|
| 29 |
- kind = "A" |
|
| 30 |
- case ChangeDelete: |
|
| 31 |
- kind = "D" |
|
| 32 |
- } |
|
| 33 |
- return fmt.Sprintf("%s %s", kind, change.Path)
|
|
| 34 |
-} |
|
| 35 |
- |
|
| 36 |
-func Changes(layers []string, rw string) ([]Change, error) {
|
|
| 37 |
- var changes []Change |
|
| 38 |
- err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
|
|
| 39 |
- if err != nil {
|
|
| 40 |
- return err |
|
| 41 |
- } |
|
| 42 |
- |
|
| 43 |
- // Rebase path |
|
| 44 |
- path, err = filepath.Rel(rw, path) |
|
| 45 |
- if err != nil {
|
|
| 46 |
- return err |
|
| 47 |
- } |
|
| 48 |
- path = filepath.Join("/", path)
|
|
| 49 |
- |
|
| 50 |
- // Skip root |
|
| 51 |
- if path == "/" {
|
|
| 52 |
- return nil |
|
| 53 |
- } |
|
| 54 |
- |
|
| 55 |
- // Skip AUFS metadata |
|
| 56 |
- if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
|
|
| 57 |
- return err |
|
| 58 |
- } |
|
| 59 |
- |
|
| 60 |
- change := Change{
|
|
| 61 |
- Path: path, |
|
| 62 |
- } |
|
| 63 |
- |
|
| 64 |
- // Find out what kind of modification happened |
|
| 65 |
- file := filepath.Base(path) |
|
| 66 |
- // If there is a whiteout, then the file was removed |
|
| 67 |
- if strings.HasPrefix(file, ".wh.") {
|
|
| 68 |
- originalFile := strings.TrimLeft(file, ".wh.") |
|
| 69 |
- change.Path = filepath.Join(filepath.Dir(path), originalFile) |
|
| 70 |
- change.Kind = ChangeDelete |
|
| 71 |
- } else {
|
|
| 72 |
- // Otherwise, the file was added |
|
| 73 |
- change.Kind = ChangeAdd |
|
| 74 |
- |
|
| 75 |
- // ...Unless it already existed in a top layer, in which case, it's a modification |
|
| 76 |
- for _, layer := range layers {
|
|
| 77 |
- stat, err := os.Stat(filepath.Join(layer, path)) |
|
| 78 |
- if err != nil && !os.IsNotExist(err) {
|
|
| 79 |
- return err |
|
| 80 |
- } |
|
| 81 |
- if err == nil {
|
|
| 82 |
- // The file existed in the top layer, so that's a modification |
|
| 83 |
- |
|
| 84 |
- // However, if it's a directory, maybe it wasn't actually modified. |
|
| 85 |
- // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar |
|
| 86 |
- if stat.IsDir() && f.IsDir() {
|
|
| 87 |
- if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
|
|
| 88 |
- // Both directories are the same, don't record the change |
|
| 89 |
- return nil |
|
| 90 |
- } |
|
| 91 |
- } |
|
| 92 |
- change.Kind = ChangeModify |
|
| 93 |
- break |
|
| 94 |
- } |
|
| 95 |
- } |
|
| 96 |
- } |
|
| 97 |
- |
|
| 98 |
- // Record change |
|
| 99 |
- changes = append(changes, change) |
|
| 100 |
- return nil |
|
| 101 |
- }) |
|
| 102 |
- if err != nil {
|
|
| 103 |
- return nil, err |
|
| 104 |
- } |
|
| 105 |
- return changes, nil |
|
| 106 |
-} |
| 107 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,157 +0,0 @@ |
| 1 |
-package graph |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "io/ioutil" |
|
| 6 |
- "os" |
|
| 7 |
- "path" |
|
| 8 |
- "path/filepath" |
|
| 9 |
- "time" |
|
| 10 |
-) |
|
| 11 |
- |
|
| 12 |
-type Graph struct {
|
|
| 13 |
- Root string |
|
| 14 |
-} |
|
| 15 |
- |
|
| 16 |
-func New(root string) (*Graph, error) {
|
|
| 17 |
- abspath, err := filepath.Abs(root) |
|
| 18 |
- if err != nil {
|
|
| 19 |
- return nil, err |
|
| 20 |
- } |
|
| 21 |
- // Create the root directory if it doesn't exists |
|
| 22 |
- if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
|
|
| 23 |
- return nil, err |
|
| 24 |
- } |
|
| 25 |
- return &Graph{
|
|
| 26 |
- Root: abspath, |
|
| 27 |
- }, nil |
|
| 28 |
-} |
|
| 29 |
- |
|
| 30 |
-func (graph *Graph) Exists(id string) bool {
|
|
| 31 |
- if _, err := graph.Get(id); err != nil {
|
|
| 32 |
- return false |
|
| 33 |
- } |
|
| 34 |
- return true |
|
| 35 |
-} |
|
| 36 |
- |
|
| 37 |
-func (graph *Graph) Get(id string) (*Image, error) {
|
|
| 38 |
- img, err := LoadImage(graph.imageRoot(id)) |
|
| 39 |
- if err != nil {
|
|
| 40 |
- return nil, err |
|
| 41 |
- } |
|
| 42 |
- if img.Id != id {
|
|
| 43 |
- return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.Id)
|
|
| 44 |
- } |
|
| 45 |
- img.graph = graph |
|
| 46 |
- return img, nil |
|
| 47 |
-} |
|
| 48 |
- |
|
| 49 |
-func (graph *Graph) Create(layerData Archive, parent, comment string) (*Image, error) {
|
|
| 50 |
- img := &Image{
|
|
| 51 |
- Id: GenerateId(), |
|
| 52 |
- Parent: parent, |
|
| 53 |
- Comment: comment, |
|
| 54 |
- Created: time.Now(), |
|
| 55 |
- } |
|
| 56 |
- if err := graph.Register(layerData, img); err != nil {
|
|
| 57 |
- return nil, err |
|
| 58 |
- } |
|
| 59 |
- return img, nil |
|
| 60 |
-} |
|
| 61 |
- |
|
| 62 |
-func (graph *Graph) Register(layerData Archive, img *Image) error {
|
|
| 63 |
- if err := ValidateId(img.Id); err != nil {
|
|
| 64 |
- return err |
|
| 65 |
- } |
|
| 66 |
- // (This is a convenience to save time. Race conditions are taken care of by os.Rename) |
|
| 67 |
- if graph.Exists(img.Id) {
|
|
| 68 |
- return fmt.Errorf("Image %s already exists", img.Id)
|
|
| 69 |
- } |
|
| 70 |
- tmp, err := graph.Mktemp(img.Id) |
|
| 71 |
- defer os.RemoveAll(tmp) |
|
| 72 |
- if err != nil {
|
|
| 73 |
- return fmt.Errorf("Mktemp failed: %s", err)
|
|
| 74 |
- } |
|
| 75 |
- if err := StoreImage(img, layerData, tmp); err != nil {
|
|
| 76 |
- return err |
|
| 77 |
- } |
|
| 78 |
- // Commit |
|
| 79 |
- if err := os.Rename(tmp, graph.imageRoot(img.Id)); err != nil {
|
|
| 80 |
- return err |
|
| 81 |
- } |
|
| 82 |
- img.graph = graph |
|
| 83 |
- return nil |
|
| 84 |
-} |
|
| 85 |
- |
|
| 86 |
-func (graph *Graph) Mktemp(id string) (string, error) {
|
|
| 87 |
- tmp, err := New(path.Join(graph.Root, ":tmp:")) |
|
| 88 |
- if err != nil {
|
|
| 89 |
- return "", fmt.Errorf("Couldn't create temp: %s", err)
|
|
| 90 |
- } |
|
| 91 |
- if tmp.Exists(id) {
|
|
| 92 |
- return "", fmt.Errorf("Image %d already exists", id)
|
|
| 93 |
- } |
|
| 94 |
- return tmp.imageRoot(id), nil |
|
| 95 |
-} |
|
| 96 |
- |
|
| 97 |
-func (graph *Graph) Garbage() (*Graph, error) {
|
|
| 98 |
- return New(path.Join(graph.Root, ":garbage:")) |
|
| 99 |
-} |
|
| 100 |
- |
|
| 101 |
-func (graph *Graph) Delete(id string) error {
|
|
| 102 |
- garbage, err := graph.Garbage() |
|
| 103 |
- if err != nil {
|
|
| 104 |
- return err |
|
| 105 |
- } |
|
| 106 |
- return os.Rename(graph.imageRoot(id), garbage.imageRoot(id)) |
|
| 107 |
-} |
|
| 108 |
- |
|
| 109 |
-func (graph *Graph) Undelete(id string) error {
|
|
| 110 |
- garbage, err := graph.Garbage() |
|
| 111 |
- if err != nil {
|
|
| 112 |
- return err |
|
| 113 |
- } |
|
| 114 |
- return os.Rename(garbage.imageRoot(id), graph.imageRoot(id)) |
|
| 115 |
-} |
|
| 116 |
- |
|
| 117 |
-func (graph *Graph) GarbageCollect() error {
|
|
| 118 |
- garbage, err := graph.Garbage() |
|
| 119 |
- if err != nil {
|
|
| 120 |
- return err |
|
| 121 |
- } |
|
| 122 |
- return os.RemoveAll(garbage.Root) |
|
| 123 |
-} |
|
| 124 |
- |
|
| 125 |
-func (graph *Graph) Map() (map[string]*Image, error) {
|
|
| 126 |
- // FIXME: this should replace All() |
|
| 127 |
- all, err := graph.All() |
|
| 128 |
- if err != nil {
|
|
| 129 |
- return nil, err |
|
| 130 |
- } |
|
| 131 |
- images := make(map[string]*Image, len(all)) |
|
| 132 |
- for _, image := range all {
|
|
| 133 |
- images[image.Id] = image |
|
| 134 |
- } |
|
| 135 |
- return images, nil |
|
| 136 |
-} |
|
| 137 |
- |
|
| 138 |
-func (graph *Graph) All() ([]*Image, error) {
|
|
| 139 |
- files, err := ioutil.ReadDir(graph.Root) |
|
| 140 |
- if err != nil {
|
|
| 141 |
- return nil, err |
|
| 142 |
- } |
|
| 143 |
- var images []*Image |
|
| 144 |
- for _, st := range files {
|
|
| 145 |
- if img, err := graph.Get(st.Name()); err != nil {
|
|
| 146 |
- // Skip image |
|
| 147 |
- continue |
|
| 148 |
- } else {
|
|
| 149 |
- images = append(images, img) |
|
| 150 |
- } |
|
| 151 |
- } |
|
| 152 |
- return images, nil |
|
| 153 |
-} |
|
| 154 |
- |
|
| 155 |
-func (graph *Graph) imageRoot(id string) string {
|
|
| 156 |
- return path.Join(graph.Root, id) |
|
| 157 |
-} |
| 158 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,210 +0,0 @@ |
| 1 |
-package graph |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "archive/tar" |
|
| 5 |
- "bytes" |
|
| 6 |
- "io" |
|
| 7 |
- "io/ioutil" |
|
| 8 |
- "os" |
|
| 9 |
- "path" |
|
| 10 |
- "testing" |
|
| 11 |
- "time" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-func TestInit(t *testing.T) {
|
|
| 15 |
- graph := tempGraph(t) |
|
| 16 |
- defer os.RemoveAll(graph.Root) |
|
| 17 |
- // Root should exist |
|
| 18 |
- if _, err := os.Stat(graph.Root); err != nil {
|
|
| 19 |
- t.Fatal(err) |
|
| 20 |
- } |
|
| 21 |
- // All() should be empty |
|
| 22 |
- if l, err := graph.All(); err != nil {
|
|
| 23 |
- t.Fatal(err) |
|
| 24 |
- } else if len(l) != 0 {
|
|
| 25 |
- t.Fatalf("List() should return %d, not %d", 0, len(l))
|
|
| 26 |
- } |
|
| 27 |
-} |
|
| 28 |
- |
|
| 29 |
-// FIXME: Do more extensive tests (ex: create multiple, delete, recreate; |
|
| 30 |
-// create multiple, check the amount of images and paths, etc..) |
|
| 31 |
-func TestCreate(t *testing.T) {
|
|
| 32 |
- graph := tempGraph(t) |
|
| 33 |
- defer os.RemoveAll(graph.Root) |
|
| 34 |
- archive, err := fakeTar() |
|
| 35 |
- if err != nil {
|
|
| 36 |
- t.Fatal(err) |
|
| 37 |
- } |
|
| 38 |
- image, err := graph.Create(archive, "", "Testing") |
|
| 39 |
- if err != nil {
|
|
| 40 |
- t.Fatal(err) |
|
| 41 |
- } |
|
| 42 |
- if err := ValidateId(image.Id); err != nil {
|
|
| 43 |
- t.Fatal(err) |
|
| 44 |
- } |
|
| 45 |
- if image.Comment != "Testing" {
|
|
| 46 |
- t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", image.Comment)
|
|
| 47 |
- } |
|
| 48 |
- if images, err := graph.All(); err != nil {
|
|
| 49 |
- t.Fatal(err) |
|
| 50 |
- } else if l := len(images); l != 1 {
|
|
| 51 |
- t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
|
| 52 |
- } |
|
| 53 |
-} |
|
| 54 |
- |
|
| 55 |
-func TestRegister(t *testing.T) {
|
|
| 56 |
- graph := tempGraph(t) |
|
| 57 |
- defer os.RemoveAll(graph.Root) |
|
| 58 |
- archive, err := fakeTar() |
|
| 59 |
- if err != nil {
|
|
| 60 |
- t.Fatal(err) |
|
| 61 |
- } |
|
| 62 |
- image := &Image{
|
|
| 63 |
- Id: GenerateId(), |
|
| 64 |
- Comment: "testing", |
|
| 65 |
- Created: time.Now(), |
|
| 66 |
- } |
|
| 67 |
- err = graph.Register(archive, image) |
|
| 68 |
- if err != nil {
|
|
| 69 |
- t.Fatal(err) |
|
| 70 |
- } |
|
| 71 |
- if images, err := graph.All(); err != nil {
|
|
| 72 |
- t.Fatal(err) |
|
| 73 |
- } else if l := len(images); l != 1 {
|
|
| 74 |
- t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
|
| 75 |
- } |
|
| 76 |
- if resultImg, err := graph.Get(image.Id); err != nil {
|
|
| 77 |
- t.Fatal(err) |
|
| 78 |
- } else {
|
|
| 79 |
- if resultImg.Id != image.Id {
|
|
| 80 |
- t.Fatalf("Wrong image ID. Should be '%s', not '%s'", image.Id, resultImg.Id)
|
|
| 81 |
- } |
|
| 82 |
- if resultImg.Comment != image.Comment {
|
|
| 83 |
- t.Fatalf("Wrong image comment. Should be '%s', not '%s'", image.Comment, resultImg.Comment)
|
|
| 84 |
- } |
|
| 85 |
- } |
|
| 86 |
-} |
|
| 87 |
- |
|
| 88 |
-func TestMount(t *testing.T) {
|
|
| 89 |
- graph := tempGraph(t) |
|
| 90 |
- defer os.RemoveAll(graph.Root) |
|
| 91 |
- archive, err := fakeTar() |
|
| 92 |
- if err != nil {
|
|
| 93 |
- t.Fatal(err) |
|
| 94 |
- } |
|
| 95 |
- image, err := graph.Create(archive, "", "Testing") |
|
| 96 |
- if err != nil {
|
|
| 97 |
- t.Fatal(err) |
|
| 98 |
- } |
|
| 99 |
- tmp, err := ioutil.TempDir("", "docker-test-graph-mount-")
|
|
| 100 |
- if err != nil {
|
|
| 101 |
- t.Fatal(err) |
|
| 102 |
- } |
|
| 103 |
- defer os.RemoveAll(tmp) |
|
| 104 |
- rootfs := path.Join(tmp, "rootfs") |
|
| 105 |
- if err := os.MkdirAll(rootfs, 0700); err != nil {
|
|
| 106 |
- t.Fatal(err) |
|
| 107 |
- } |
|
| 108 |
- rw := path.Join(tmp, "rw") |
|
| 109 |
- if err := os.MkdirAll(rw, 0700); err != nil {
|
|
| 110 |
- t.Fatal(err) |
|
| 111 |
- } |
|
| 112 |
- if err := image.Mount(rootfs, rw); err != nil {
|
|
| 113 |
- t.Fatal(err) |
|
| 114 |
- } |
|
| 115 |
- // FIXME: test for mount contents |
|
| 116 |
- defer func() {
|
|
| 117 |
- if err := Unmount(rootfs); err != nil {
|
|
| 118 |
- t.Error(err) |
|
| 119 |
- } |
|
| 120 |
- }() |
|
| 121 |
-} |
|
| 122 |
- |
|
| 123 |
-func TestDelete(t *testing.T) {
|
|
| 124 |
- graph := tempGraph(t) |
|
| 125 |
- defer os.RemoveAll(graph.Root) |
|
| 126 |
- archive, err := fakeTar() |
|
| 127 |
- if err != nil {
|
|
| 128 |
- t.Fatal(err) |
|
| 129 |
- } |
|
| 130 |
- assertNImages(graph, t, 0) |
|
| 131 |
- img, err := graph.Create(archive, "", "Bla bla") |
|
| 132 |
- if err != nil {
|
|
| 133 |
- t.Fatal(err) |
|
| 134 |
- } |
|
| 135 |
- assertNImages(graph, t, 1) |
|
| 136 |
- if err := graph.Delete(img.Id); err != nil {
|
|
| 137 |
- t.Fatal(err) |
|
| 138 |
- } |
|
| 139 |
- assertNImages(graph, t, 0) |
|
| 140 |
- |
|
| 141 |
- // Test 2 create (same name) / 1 delete |
|
| 142 |
- img1, err := graph.Create(archive, "foo", "Testing") |
|
| 143 |
- if err != nil {
|
|
| 144 |
- t.Fatal(err) |
|
| 145 |
- } |
|
| 146 |
- if _, err = graph.Create(archive, "foo", "Testing"); err != nil {
|
|
| 147 |
- t.Fatal(err) |
|
| 148 |
- } |
|
| 149 |
- assertNImages(graph, t, 2) |
|
| 150 |
- if err := graph.Delete(img1.Id); err != nil {
|
|
| 151 |
- t.Fatal(err) |
|
| 152 |
- } |
|
| 153 |
- assertNImages(graph, t, 1) |
|
| 154 |
- |
|
| 155 |
- // Test delete wrong name |
|
| 156 |
- if err := graph.Delete("Not_foo"); err == nil {
|
|
| 157 |
- t.Fatalf("Deleting wrong ID should return an error")
|
|
| 158 |
- } |
|
| 159 |
- assertNImages(graph, t, 1) |
|
| 160 |
- |
|
| 161 |
-} |
|
| 162 |
- |
|
| 163 |
-func assertNImages(graph *Graph, t *testing.T, n int) {
|
|
| 164 |
- if images, err := graph.All(); err != nil {
|
|
| 165 |
- t.Fatal(err) |
|
| 166 |
- } else if actualN := len(images); actualN != n {
|
|
| 167 |
- t.Fatalf("Expected %d images, found %d", n, actualN)
|
|
| 168 |
- } |
|
| 169 |
-} |
|
| 170 |
- |
|
| 171 |
-/* |
|
| 172 |
- * HELPER FUNCTIONS |
|
| 173 |
- */ |
|
| 174 |
- |
|
| 175 |
-func tempGraph(t *testing.T) *Graph {
|
|
| 176 |
- tmp, err := ioutil.TempDir("", "docker-graph-")
|
|
| 177 |
- if err != nil {
|
|
| 178 |
- t.Fatal(err) |
|
| 179 |
- } |
|
| 180 |
- graph, err := New(tmp) |
|
| 181 |
- if err != nil {
|
|
| 182 |
- t.Fatal(err) |
|
| 183 |
- } |
|
| 184 |
- return graph |
|
| 185 |
-} |
|
| 186 |
- |
|
| 187 |
-func testArchive(t *testing.T) Archive {
|
|
| 188 |
- archive, err := fakeTar() |
|
| 189 |
- if err != nil {
|
|
| 190 |
- t.Fatal(err) |
|
| 191 |
- } |
|
| 192 |
- return archive |
|
| 193 |
-} |
|
| 194 |
- |
|
| 195 |
-func fakeTar() (io.Reader, error) {
|
|
| 196 |
- content := []byte("Hello world!\n")
|
|
| 197 |
- buf := new(bytes.Buffer) |
|
| 198 |
- tw := tar.NewWriter(buf) |
|
| 199 |
- for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
|
|
| 200 |
- hdr := new(tar.Header) |
|
| 201 |
- hdr.Size = int64(len(content)) |
|
| 202 |
- hdr.Name = name |
|
| 203 |
- if err := tw.WriteHeader(hdr); err != nil {
|
|
| 204 |
- return nil, err |
|
| 205 |
- } |
|
| 206 |
- tw.Write([]byte(content)) |
|
| 207 |
- } |
|
| 208 |
- tw.Close() |
|
| 209 |
- return buf, nil |
|
| 210 |
-} |
| 211 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,257 +0,0 @@ |
| 1 |
-package graph |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bytes" |
|
| 5 |
- "crypto/sha256" |
|
| 6 |
- "encoding/json" |
|
| 7 |
- "fmt" |
|
| 8 |
- "io" |
|
| 9 |
- "io/ioutil" |
|
| 10 |
- "math/rand" |
|
| 11 |
- "os" |
|
| 12 |
- "path" |
|
| 13 |
- "strings" |
|
| 14 |
- "time" |
|
| 15 |
-) |
|
| 16 |
- |
|
| 17 |
-type Image struct {
|
|
| 18 |
- Id string |
|
| 19 |
- Parent string |
|
| 20 |
- Comment string |
|
| 21 |
- Created time.Time |
|
| 22 |
- graph *Graph |
|
| 23 |
-} |
|
| 24 |
- |
|
| 25 |
-func LoadImage(root string) (*Image, error) {
|
|
| 26 |
- // Load the json data |
|
| 27 |
- jsonData, err := ioutil.ReadFile(jsonPath(root)) |
|
| 28 |
- if err != nil {
|
|
| 29 |
- return nil, err |
|
| 30 |
- } |
|
| 31 |
- var img Image |
|
| 32 |
- if err := json.Unmarshal(jsonData, &img); err != nil {
|
|
| 33 |
- return nil, err |
|
| 34 |
- } |
|
| 35 |
- if err := ValidateId(img.Id); err != nil {
|
|
| 36 |
- return nil, err |
|
| 37 |
- } |
|
| 38 |
- // Check that the filesystem layer exists |
|
| 39 |
- if stat, err := os.Stat(layerPath(root)); err != nil {
|
|
| 40 |
- if os.IsNotExist(err) {
|
|
| 41 |
- return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.Id)
|
|
| 42 |
- } else {
|
|
| 43 |
- return nil, err |
|
| 44 |
- } |
|
| 45 |
- } else if !stat.IsDir() {
|
|
| 46 |
- return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.Id, layerPath(root))
|
|
| 47 |
- } |
|
| 48 |
- return &img, nil |
|
| 49 |
-} |
|
| 50 |
- |
|
| 51 |
-func StoreImage(img *Image, layerData Archive, root string) error {
|
|
| 52 |
- // Check that root doesn't already exist |
|
| 53 |
- if _, err := os.Stat(root); err == nil {
|
|
| 54 |
- return fmt.Errorf("Image %s already exists", img.Id)
|
|
| 55 |
- } else if !os.IsNotExist(err) {
|
|
| 56 |
- return err |
|
| 57 |
- } |
|
| 58 |
- // Store the layer |
|
| 59 |
- layer := layerPath(root) |
|
| 60 |
- if err := os.MkdirAll(layer, 0700); err != nil {
|
|
| 61 |
- return err |
|
| 62 |
- } |
|
| 63 |
- if err := Untar(layerData, layer); err != nil {
|
|
| 64 |
- return err |
|
| 65 |
- } |
|
| 66 |
- // Store the json ball |
|
| 67 |
- jsonData, err := json.Marshal(img) |
|
| 68 |
- if err != nil {
|
|
| 69 |
- return err |
|
| 70 |
- } |
|
| 71 |
- if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
|
|
| 72 |
- return err |
|
| 73 |
- } |
|
| 74 |
- return nil |
|
| 75 |
-} |
|
| 76 |
- |
|
| 77 |
-func layerPath(root string) string {
|
|
| 78 |
- return path.Join(root, "layer") |
|
| 79 |
-} |
|
| 80 |
- |
|
| 81 |
-func jsonPath(root string) string {
|
|
| 82 |
- return path.Join(root, "json") |
|
| 83 |
-} |
|
| 84 |
- |
|
| 85 |
-func MountAUFS(ro []string, rw string, target string) error {
|
|
| 86 |
- // FIXME: Now mount the layers |
|
| 87 |
- rwBranch := fmt.Sprintf("%v=rw", rw)
|
|
| 88 |
- roBranches := "" |
|
| 89 |
- for _, layer := range ro {
|
|
| 90 |
- roBranches += fmt.Sprintf("%v=ro:", layer)
|
|
| 91 |
- } |
|
| 92 |
- branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
|
|
| 93 |
- return mount("none", target, "aufs", 0, branches)
|
|
| 94 |
-} |
|
| 95 |
- |
|
| 96 |
-func (image *Image) Mount(root, rw string) error {
|
|
| 97 |
- if mounted, err := Mounted(root); err != nil {
|
|
| 98 |
- return err |
|
| 99 |
- } else if mounted {
|
|
| 100 |
- return fmt.Errorf("%s is already mounted", root)
|
|
| 101 |
- } |
|
| 102 |
- layers, err := image.layers() |
|
| 103 |
- if err != nil {
|
|
| 104 |
- return err |
|
| 105 |
- } |
|
| 106 |
- // Create the target directories if they don't exist |
|
| 107 |
- if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) {
|
|
| 108 |
- return err |
|
| 109 |
- } |
|
| 110 |
- if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {
|
|
| 111 |
- return err |
|
| 112 |
- } |
|
| 113 |
- // FIXME: @creack shouldn't we do this after going over changes? |
|
| 114 |
- if err := MountAUFS(layers, rw, root); err != nil {
|
|
| 115 |
- return err |
|
| 116 |
- } |
|
| 117 |
- // FIXME: Create tests for deletion |
|
| 118 |
- // FIXME: move this part to change.go |
|
| 119 |
- // Retrieve the changeset from the parent and apply it to the container |
|
| 120 |
- // - Retrieve the changes |
|
| 121 |
- changes, err := Changes(layers, layers[0]) |
|
| 122 |
- if err != nil {
|
|
| 123 |
- return err |
|
| 124 |
- } |
|
| 125 |
- // Iterate on changes |
|
| 126 |
- for _, c := range changes {
|
|
| 127 |
- // If there is a delete |
|
| 128 |
- if c.Kind == ChangeDelete {
|
|
| 129 |
- // Make sure the directory exists |
|
| 130 |
- file_path, file_name := path.Dir(c.Path), path.Base(c.Path) |
|
| 131 |
- if err := os.MkdirAll(path.Join(rw, file_path), 0755); err != nil {
|
|
| 132 |
- return err |
|
| 133 |
- } |
|
| 134 |
- // And create the whiteout (we just need to create empty file, discard the return) |
|
| 135 |
- if _, err := os.Create(path.Join(path.Join(rw, file_path), |
|
| 136 |
- ".wh."+path.Base(file_name))); err != nil {
|
|
| 137 |
- return err |
|
| 138 |
- } |
|
| 139 |
- } |
|
| 140 |
- } |
|
| 141 |
- return nil |
|
| 142 |
-} |
|
| 143 |
- |
|
| 144 |
-func (image *Image) Changes(rw string) ([]Change, error) {
|
|
| 145 |
- layers, err := image.layers() |
|
| 146 |
- if err != nil {
|
|
| 147 |
- return nil, err |
|
| 148 |
- } |
|
| 149 |
- return Changes(layers, rw) |
|
| 150 |
-} |
|
| 151 |
- |
|
| 152 |
-func ValidateId(id string) error {
|
|
| 153 |
- if id == "" {
|
|
| 154 |
- return fmt.Errorf("Image id can't be empty")
|
|
| 155 |
- } |
|
| 156 |
- if strings.Contains(id, ":") {
|
|
| 157 |
- return fmt.Errorf("Invalid character in image id: ':'")
|
|
| 158 |
- } |
|
| 159 |
- return nil |
|
| 160 |
-} |
|
| 161 |
- |
|
| 162 |
-func GenerateId() string {
|
|
| 163 |
- // FIXME: don't seed every time |
|
| 164 |
- rand.Seed(time.Now().UTC().UnixNano()) |
|
| 165 |
- randomBytes := bytes.NewBuffer([]byte(fmt.Sprintf("%x", rand.Int())))
|
|
| 166 |
- id, _ := ComputeId(randomBytes) // can't fail |
|
| 167 |
- return id |
|
| 168 |
-} |
|
| 169 |
- |
|
| 170 |
-// ComputeId reads from `content` until EOF, then returns a SHA of what it read, as a string. |
|
| 171 |
-func ComputeId(content io.Reader) (string, error) {
|
|
| 172 |
- h := sha256.New() |
|
| 173 |
- if _, err := io.Copy(h, content); err != nil {
|
|
| 174 |
- return "", err |
|
| 175 |
- } |
|
| 176 |
- return fmt.Sprintf("%x", h.Sum(nil)[:8]), nil
|
|
| 177 |
-} |
|
| 178 |
- |
|
| 179 |
-// Image includes convenience proxy functions to its graph |
|
| 180 |
-// These functions will return an error if the image is not registered |
|
| 181 |
-// (ie. if image.graph == nil) |
|
| 182 |
- |
|
| 183 |
-func (img *Image) History() ([]*Image, error) {
|
|
| 184 |
- var parents []*Image |
|
| 185 |
- if err := img.WalkHistory( |
|
| 186 |
- func(img *Image) {
|
|
| 187 |
- parents = append(parents, img) |
|
| 188 |
- }, |
|
| 189 |
- ); err != nil {
|
|
| 190 |
- return nil, err |
|
| 191 |
- } |
|
| 192 |
- return parents, nil |
|
| 193 |
-} |
|
| 194 |
- |
|
| 195 |
-// layers returns all the filesystem layers needed to mount an image |
|
| 196 |
-func (img *Image) layers() ([]string, error) {
|
|
| 197 |
- var list []string |
|
| 198 |
- var e error |
|
| 199 |
- if err := img.WalkHistory( |
|
| 200 |
- func(img *Image) {
|
|
| 201 |
- if layer, err := img.layer(); err != nil {
|
|
| 202 |
- e = err |
|
| 203 |
- } else if layer != "" {
|
|
| 204 |
- list = append(list, layer) |
|
| 205 |
- } |
|
| 206 |
- }, |
|
| 207 |
- ); err != nil {
|
|
| 208 |
- return nil, err |
|
| 209 |
- } else if e != nil { // Did an error occur inside the handler?
|
|
| 210 |
- return nil, e |
|
| 211 |
- } |
|
| 212 |
- if len(list) == 0 {
|
|
| 213 |
- return nil, fmt.Errorf("No layer found for image %s\n", img.Id)
|
|
| 214 |
- } |
|
| 215 |
- return list, nil |
|
| 216 |
-} |
|
| 217 |
- |
|
| 218 |
-func (img *Image) WalkHistory(handler func(*Image)) error {
|
|
| 219 |
- var err error |
|
| 220 |
- currentImg := img |
|
| 221 |
- for currentImg != nil {
|
|
| 222 |
- if handler != nil {
|
|
| 223 |
- handler(currentImg) |
|
| 224 |
- } |
|
| 225 |
- currentImg, err = currentImg.GetParent() |
|
| 226 |
- if err != nil {
|
|
| 227 |
- return fmt.Errorf("Error while getting parent image: %v", err)
|
|
| 228 |
- } |
|
| 229 |
- } |
|
| 230 |
- return nil |
|
| 231 |
-} |
|
| 232 |
- |
|
| 233 |
-func (img *Image) GetParent() (*Image, error) {
|
|
| 234 |
- if img.Parent == "" {
|
|
| 235 |
- return nil, nil |
|
| 236 |
- } |
|
| 237 |
- if img.graph == nil {
|
|
| 238 |
- return nil, fmt.Errorf("Can't lookup parent of unregistered image")
|
|
| 239 |
- } |
|
| 240 |
- return img.graph.Get(img.Parent) |
|
| 241 |
-} |
|
| 242 |
- |
|
| 243 |
-func (img *Image) root() (string, error) {
|
|
| 244 |
- if img.graph == nil {
|
|
| 245 |
- return "", fmt.Errorf("Can't lookup root of unregistered image")
|
|
| 246 |
- } |
|
| 247 |
- return img.graph.imageRoot(img.Id), nil |
|
| 248 |
-} |
|
| 249 |
- |
|
| 250 |
-// Return the path of an image's layer |
|
| 251 |
-func (img *Image) layer() (string, error) {
|
|
| 252 |
- root, err := img.root() |
|
| 253 |
- if err != nil {
|
|
| 254 |
- return "", err |
|
| 255 |
- } |
|
| 256 |
- return layerPath(root), nil |
|
| 257 |
-} |
| 258 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,48 +0,0 @@ |
| 1 |
-package graph |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "os" |
|
| 6 |
- "path/filepath" |
|
| 7 |
- "syscall" |
|
| 8 |
- "time" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-func Unmount(target string) error {
|
|
| 12 |
- if err := syscall.Unmount(target, 0); err != nil {
|
|
| 13 |
- return err |
|
| 14 |
- } |
|
| 15 |
- // Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint |
|
| 16 |
- // for some time. We'll just keep retrying until it succeeds. |
|
| 17 |
- for retries := 0; retries < 1000; retries++ {
|
|
| 18 |
- err := os.Remove(target) |
|
| 19 |
- if err == nil {
|
|
| 20 |
- // rm mntpoint succeeded |
|
| 21 |
- return nil |
|
| 22 |
- } |
|
| 23 |
- if os.IsNotExist(err) {
|
|
| 24 |
- // mntpoint doesn't exist anymore. Success. |
|
| 25 |
- return nil |
|
| 26 |
- } |
|
| 27 |
- // fmt.Printf("(%v) Remove %v returned: %v\n", retries, target, err)
|
|
| 28 |
- time.Sleep(10 * time.Millisecond) |
|
| 29 |
- } |
|
| 30 |
- return fmt.Errorf("Umount: Failed to umount %v", target)
|
|
| 31 |
-} |
|
| 32 |
- |
|
| 33 |
-func Mounted(mountpoint string) (bool, error) {
|
|
| 34 |
- mntpoint, err := os.Stat(mountpoint) |
|
| 35 |
- if err != nil {
|
|
| 36 |
- if os.IsNotExist(err) {
|
|
| 37 |
- return false, nil |
|
| 38 |
- } |
|
| 39 |
- return false, err |
|
| 40 |
- } |
|
| 41 |
- parent, err := os.Stat(filepath.Join(mountpoint, "..")) |
|
| 42 |
- if err != nil {
|
|
| 43 |
- return false, err |
|
| 44 |
- } |
|
| 45 |
- mntpointSt := mntpoint.Sys().(*syscall.Stat_t) |
|
| 46 |
- parentSt := parent.Sys().(*syscall.Stat_t) |
|
| 47 |
- return mntpointSt.Dev != parentSt.Dev, nil |
|
| 48 |
-} |
| 8 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,92 +0,0 @@ |
| 1 |
-package graph |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "encoding/json" |
|
| 5 |
- "io/ioutil" |
|
| 6 |
- "path/filepath" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-type TagStore struct {
|
|
| 10 |
- path string |
|
| 11 |
- graph *Graph |
|
| 12 |
- Repositories map[string]Repository |
|
| 13 |
-} |
|
| 14 |
- |
|
| 15 |
-type Repository map[string]string |
|
| 16 |
- |
|
| 17 |
-func NewTagStore(path string, graph *Graph) (*TagStore, error) {
|
|
| 18 |
- abspath, err := filepath.Abs(path) |
|
| 19 |
- if err != nil {
|
|
| 20 |
- return nil, err |
|
| 21 |
- } |
|
| 22 |
- store := &TagStore{
|
|
| 23 |
- path: abspath, |
|
| 24 |
- graph: graph, |
|
| 25 |
- Repositories: make(map[string]Repository), |
|
| 26 |
- } |
|
| 27 |
- if err := store.Save(); err != nil {
|
|
| 28 |
- return nil, err |
|
| 29 |
- } |
|
| 30 |
- return store, nil |
|
| 31 |
-} |
|
| 32 |
- |
|
| 33 |
-func (store *TagStore) Save() error {
|
|
| 34 |
- // Store the json ball |
|
| 35 |
- jsonData, err := json.Marshal(store) |
|
| 36 |
- if err != nil {
|
|
| 37 |
- return err |
|
| 38 |
- } |
|
| 39 |
- if err := ioutil.WriteFile(store.path, jsonData, 0600); err != nil {
|
|
| 40 |
- return err |
|
| 41 |
- } |
|
| 42 |
- return nil |
|
| 43 |
-} |
|
| 44 |
- |
|
| 45 |
-func (store *TagStore) Reload() error {
|
|
| 46 |
- jsonData, err := ioutil.ReadFile(store.path) |
|
| 47 |
- if err != nil {
|
|
| 48 |
- return err |
|
| 49 |
- } |
|
| 50 |
- if err := json.Unmarshal(jsonData, store); err != nil {
|
|
| 51 |
- return err |
|
| 52 |
- } |
|
| 53 |
- return nil |
|
| 54 |
-} |
|
| 55 |
- |
|
| 56 |
-func (store *TagStore) Set(repoName, tag, revision string) error {
|
|
| 57 |
- if err := store.Reload(); err != nil {
|
|
| 58 |
- return err |
|
| 59 |
- } |
|
| 60 |
- var repo Repository |
|
| 61 |
- if r, exists := store.Repositories[repoName]; exists {
|
|
| 62 |
- repo = r |
|
| 63 |
- } else {
|
|
| 64 |
- repo = make(map[string]string) |
|
| 65 |
- store.Repositories[repoName] = repo |
|
| 66 |
- } |
|
| 67 |
- repo[tag] = revision |
|
| 68 |
- return store.Save() |
|
| 69 |
-} |
|
| 70 |
- |
|
| 71 |
-func (store *TagStore) Get(repoName string) (Repository, error) {
|
|
| 72 |
- if err := store.Reload(); err != nil {
|
|
| 73 |
- return nil, err |
|
| 74 |
- } |
|
| 75 |
- if r, exists := store.Repositories[repoName]; exists {
|
|
| 76 |
- return r, nil |
|
| 77 |
- } |
|
| 78 |
- return nil, nil |
|
| 79 |
-} |
|
| 80 |
- |
|
| 81 |
-func (store *TagStore) GetImage(repoName, tag string) (*Image, error) {
|
|
| 82 |
- repo, err := store.Get(repoName) |
|
| 83 |
- if err != nil {
|
|
| 84 |
- return nil, err |
|
| 85 |
- } else if repo == nil {
|
|
| 86 |
- return nil, nil |
|
| 87 |
- } |
|
| 88 |
- if revision, exists := repo[tag]; exists {
|
|
| 89 |
- return store.graph.Get(revision) |
|
| 90 |
- } |
|
| 91 |
- return nil, nil |
|
| 92 |
-} |
| 93 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,210 @@ |
| 0 |
+package docker |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "archive/tar" |
|
| 4 |
+ "bytes" |
|
| 5 |
+ "io" |
|
| 6 |
+ "io/ioutil" |
|
| 7 |
+ "os" |
|
| 8 |
+ "path" |
|
| 9 |
+ "testing" |
|
| 10 |
+ "time" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func TestInit(t *testing.T) {
|
|
| 14 |
+ graph := tempGraph(t) |
|
| 15 |
+ defer os.RemoveAll(graph.Root) |
|
| 16 |
+ // Root should exist |
|
| 17 |
+ if _, err := os.Stat(graph.Root); err != nil {
|
|
| 18 |
+ t.Fatal(err) |
|
| 19 |
+ } |
|
| 20 |
+ // All() should be empty |
|
| 21 |
+ if l, err := graph.All(); err != nil {
|
|
| 22 |
+ t.Fatal(err) |
|
| 23 |
+ } else if len(l) != 0 {
|
|
| 24 |
+ t.Fatalf("List() should return %d, not %d", 0, len(l))
|
|
| 25 |
+ } |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+// FIXME: Do more extensive tests (ex: create multiple, delete, recreate; |
|
| 29 |
+// create multiple, check the amount of images and paths, etc..) |
|
| 30 |
+func TestGraphCreate(t *testing.T) {
|
|
| 31 |
+ graph := tempGraph(t) |
|
| 32 |
+ defer os.RemoveAll(graph.Root) |
|
| 33 |
+ archive, err := fakeTar() |
|
| 34 |
+ if err != nil {
|
|
| 35 |
+ t.Fatal(err) |
|
| 36 |
+ } |
|
| 37 |
+ image, err := graph.Create(archive, "", "Testing") |
|
| 38 |
+ if err != nil {
|
|
| 39 |
+ t.Fatal(err) |
|
| 40 |
+ } |
|
| 41 |
+ if err := ValidateId(image.Id); err != nil {
|
|
| 42 |
+ t.Fatal(err) |
|
| 43 |
+ } |
|
| 44 |
+ if image.Comment != "Testing" {
|
|
| 45 |
+ t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", image.Comment)
|
|
| 46 |
+ } |
|
| 47 |
+ if images, err := graph.All(); err != nil {
|
|
| 48 |
+ t.Fatal(err) |
|
| 49 |
+ } else if l := len(images); l != 1 {
|
|
| 50 |
+ t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
|
| 51 |
+ } |
|
| 52 |
+} |
|
| 53 |
+ |
|
| 54 |
+func TestRegister(t *testing.T) {
|
|
| 55 |
+ graph := tempGraph(t) |
|
| 56 |
+ defer os.RemoveAll(graph.Root) |
|
| 57 |
+ archive, err := fakeTar() |
|
| 58 |
+ if err != nil {
|
|
| 59 |
+ t.Fatal(err) |
|
| 60 |
+ } |
|
| 61 |
+ image := &Image{
|
|
| 62 |
+ Id: GenerateId(), |
|
| 63 |
+ Comment: "testing", |
|
| 64 |
+ Created: time.Now(), |
|
| 65 |
+ } |
|
| 66 |
+ err = graph.Register(archive, image) |
|
| 67 |
+ if err != nil {
|
|
| 68 |
+ t.Fatal(err) |
|
| 69 |
+ } |
|
| 70 |
+ if images, err := graph.All(); err != nil {
|
|
| 71 |
+ t.Fatal(err) |
|
| 72 |
+ } else if l := len(images); l != 1 {
|
|
| 73 |
+ t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
|
| 74 |
+ } |
|
| 75 |
+ if resultImg, err := graph.Get(image.Id); err != nil {
|
|
| 76 |
+ t.Fatal(err) |
|
| 77 |
+ } else {
|
|
| 78 |
+ if resultImg.Id != image.Id {
|
|
| 79 |
+ t.Fatalf("Wrong image ID. Should be '%s', not '%s'", image.Id, resultImg.Id)
|
|
| 80 |
+ } |
|
| 81 |
+ if resultImg.Comment != image.Comment {
|
|
| 82 |
+ t.Fatalf("Wrong image comment. Should be '%s', not '%s'", image.Comment, resultImg.Comment)
|
|
| 83 |
+ } |
|
| 84 |
+ } |
|
| 85 |
+} |
|
| 86 |
+ |
|
| 87 |
+func TestMount(t *testing.T) {
|
|
| 88 |
+ graph := tempGraph(t) |
|
| 89 |
+ defer os.RemoveAll(graph.Root) |
|
| 90 |
+ archive, err := fakeTar() |
|
| 91 |
+ if err != nil {
|
|
| 92 |
+ t.Fatal(err) |
|
| 93 |
+ } |
|
| 94 |
+ image, err := graph.Create(archive, "", "Testing") |
|
| 95 |
+ if err != nil {
|
|
| 96 |
+ t.Fatal(err) |
|
| 97 |
+ } |
|
| 98 |
+ tmp, err := ioutil.TempDir("", "docker-test-graph-mount-")
|
|
| 99 |
+ if err != nil {
|
|
| 100 |
+ t.Fatal(err) |
|
| 101 |
+ } |
|
| 102 |
+ defer os.RemoveAll(tmp) |
|
| 103 |
+ rootfs := path.Join(tmp, "rootfs") |
|
| 104 |
+ if err := os.MkdirAll(rootfs, 0700); err != nil {
|
|
| 105 |
+ t.Fatal(err) |
|
| 106 |
+ } |
|
| 107 |
+ rw := path.Join(tmp, "rw") |
|
| 108 |
+ if err := os.MkdirAll(rw, 0700); err != nil {
|
|
| 109 |
+ t.Fatal(err) |
|
| 110 |
+ } |
|
| 111 |
+ if err := image.Mount(rootfs, rw); err != nil {
|
|
| 112 |
+ t.Fatal(err) |
|
| 113 |
+ } |
|
| 114 |
+ // FIXME: test for mount contents |
|
| 115 |
+ defer func() {
|
|
| 116 |
+ if err := Unmount(rootfs); err != nil {
|
|
| 117 |
+ t.Error(err) |
|
| 118 |
+ } |
|
| 119 |
+ }() |
|
| 120 |
+} |
|
| 121 |
+ |
|
| 122 |
+func TestDelete(t *testing.T) {
|
|
| 123 |
+ graph := tempGraph(t) |
|
| 124 |
+ defer os.RemoveAll(graph.Root) |
|
| 125 |
+ archive, err := fakeTar() |
|
| 126 |
+ if err != nil {
|
|
| 127 |
+ t.Fatal(err) |
|
| 128 |
+ } |
|
| 129 |
+ assertNImages(graph, t, 0) |
|
| 130 |
+ img, err := graph.Create(archive, "", "Bla bla") |
|
| 131 |
+ if err != nil {
|
|
| 132 |
+ t.Fatal(err) |
|
| 133 |
+ } |
|
| 134 |
+ assertNImages(graph, t, 1) |
|
| 135 |
+ if err := graph.Delete(img.Id); err != nil {
|
|
| 136 |
+ t.Fatal(err) |
|
| 137 |
+ } |
|
| 138 |
+ assertNImages(graph, t, 0) |
|
| 139 |
+ |
|
| 140 |
+ // Test 2 create (same name) / 1 delete |
|
| 141 |
+ img1, err := graph.Create(archive, "foo", "Testing") |
|
| 142 |
+ if err != nil {
|
|
| 143 |
+ t.Fatal(err) |
|
| 144 |
+ } |
|
| 145 |
+ if _, err = graph.Create(archive, "foo", "Testing"); err != nil {
|
|
| 146 |
+ t.Fatal(err) |
|
| 147 |
+ } |
|
| 148 |
+ assertNImages(graph, t, 2) |
|
| 149 |
+ if err := graph.Delete(img1.Id); err != nil {
|
|
| 150 |
+ t.Fatal(err) |
|
| 151 |
+ } |
|
| 152 |
+ assertNImages(graph, t, 1) |
|
| 153 |
+ |
|
| 154 |
+ // Test delete wrong name |
|
| 155 |
+ if err := graph.Delete("Not_foo"); err == nil {
|
|
| 156 |
+ t.Fatalf("Deleting wrong ID should return an error")
|
|
| 157 |
+ } |
|
| 158 |
+ assertNImages(graph, t, 1) |
|
| 159 |
+ |
|
| 160 |
+} |
|
| 161 |
+ |
|
| 162 |
+func assertNImages(graph *Graph, t *testing.T, n int) {
|
|
| 163 |
+ if images, err := graph.All(); err != nil {
|
|
| 164 |
+ t.Fatal(err) |
|
| 165 |
+ } else if actualN := len(images); actualN != n {
|
|
| 166 |
+ t.Fatalf("Expected %d images, found %d", n, actualN)
|
|
| 167 |
+ } |
|
| 168 |
+} |
|
| 169 |
+ |
|
| 170 |
+/* |
|
| 171 |
+ * HELPER FUNCTIONS |
|
| 172 |
+ */ |
|
| 173 |
+ |
|
| 174 |
+func tempGraph(t *testing.T) *Graph {
|
|
| 175 |
+ tmp, err := ioutil.TempDir("", "docker-graph-")
|
|
| 176 |
+ if err != nil {
|
|
| 177 |
+ t.Fatal(err) |
|
| 178 |
+ } |
|
| 179 |
+ graph, err := NewGraph(tmp) |
|
| 180 |
+ if err != nil {
|
|
| 181 |
+ t.Fatal(err) |
|
| 182 |
+ } |
|
| 183 |
+ return graph |
|
| 184 |
+} |
|
| 185 |
+ |
|
| 186 |
+func testArchive(t *testing.T) Archive {
|
|
| 187 |
+ archive, err := fakeTar() |
|
| 188 |
+ if err != nil {
|
|
| 189 |
+ t.Fatal(err) |
|
| 190 |
+ } |
|
| 191 |
+ return archive |
|
| 192 |
+} |
|
| 193 |
+ |
|
| 194 |
+func fakeTar() (io.Reader, error) {
|
|
| 195 |
+ content := []byte("Hello world!\n")
|
|
| 196 |
+ buf := new(bytes.Buffer) |
|
| 197 |
+ tw := tar.NewWriter(buf) |
|
| 198 |
+ for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
|
|
| 199 |
+ hdr := new(tar.Header) |
|
| 200 |
+ hdr.Size = int64(len(content)) |
|
| 201 |
+ hdr.Name = name |
|
| 202 |
+ if err := tw.WriteHeader(hdr); err != nil {
|
|
| 203 |
+ return nil, err |
|
| 204 |
+ } |
|
| 205 |
+ tw.Write([]byte(content)) |
|
| 206 |
+ } |
|
| 207 |
+ tw.Close() |
|
| 208 |
+ return buf, nil |
|
| 209 |
+} |
| 0 | 210 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,257 @@ |
| 0 |
+package docker |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "crypto/sha256" |
|
| 5 |
+ "encoding/json" |
|
| 6 |
+ "fmt" |
|
| 7 |
+ "io" |
|
| 8 |
+ "io/ioutil" |
|
| 9 |
+ "math/rand" |
|
| 10 |
+ "os" |
|
| 11 |
+ "path" |
|
| 12 |
+ "strings" |
|
| 13 |
+ "time" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+type Image struct {
|
|
| 17 |
+ Id string |
|
| 18 |
+ Parent string |
|
| 19 |
+ Comment string |
|
| 20 |
+ Created time.Time |
|
| 21 |
+ graph *Graph |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+func LoadImage(root string) (*Image, error) {
|
|
| 25 |
+ // Load the json data |
|
| 26 |
+ jsonData, err := ioutil.ReadFile(jsonPath(root)) |
|
| 27 |
+ if err != nil {
|
|
| 28 |
+ return nil, err |
|
| 29 |
+ } |
|
| 30 |
+ var img Image |
|
| 31 |
+ if err := json.Unmarshal(jsonData, &img); err != nil {
|
|
| 32 |
+ return nil, err |
|
| 33 |
+ } |
|
| 34 |
+ if err := ValidateId(img.Id); err != nil {
|
|
| 35 |
+ return nil, err |
|
| 36 |
+ } |
|
| 37 |
+ // Check that the filesystem layer exists |
|
| 38 |
+ if stat, err := os.Stat(layerPath(root)); err != nil {
|
|
| 39 |
+ if os.IsNotExist(err) {
|
|
| 40 |
+ return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.Id)
|
|
| 41 |
+ } else {
|
|
| 42 |
+ return nil, err |
|
| 43 |
+ } |
|
| 44 |
+ } else if !stat.IsDir() {
|
|
| 45 |
+ return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.Id, layerPath(root))
|
|
| 46 |
+ } |
|
| 47 |
+ return &img, nil |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+func StoreImage(img *Image, layerData Archive, root string) error {
|
|
| 51 |
+ // Check that root doesn't already exist |
|
| 52 |
+ if _, err := os.Stat(root); err == nil {
|
|
| 53 |
+ return fmt.Errorf("Image %s already exists", img.Id)
|
|
| 54 |
+ } else if !os.IsNotExist(err) {
|
|
| 55 |
+ return err |
|
| 56 |
+ } |
|
| 57 |
+ // Store the layer |
|
| 58 |
+ layer := layerPath(root) |
|
| 59 |
+ if err := os.MkdirAll(layer, 0700); err != nil {
|
|
| 60 |
+ return err |
|
| 61 |
+ } |
|
| 62 |
+ if err := Untar(layerData, layer); err != nil {
|
|
| 63 |
+ return err |
|
| 64 |
+ } |
|
| 65 |
+ // Store the json ball |
|
| 66 |
+ jsonData, err := json.Marshal(img) |
|
| 67 |
+ if err != nil {
|
|
| 68 |
+ return err |
|
| 69 |
+ } |
|
| 70 |
+ if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
|
|
| 71 |
+ return err |
|
| 72 |
+ } |
|
| 73 |
+ return nil |
|
| 74 |
+} |
|
| 75 |
+ |
|
| 76 |
+func layerPath(root string) string {
|
|
| 77 |
+ return path.Join(root, "layer") |
|
| 78 |
+} |
|
| 79 |
+ |
|
| 80 |
+func jsonPath(root string) string {
|
|
| 81 |
+ return path.Join(root, "json") |
|
| 82 |
+} |
|
| 83 |
+ |
|
| 84 |
+func MountAUFS(ro []string, rw string, target string) error {
|
|
| 85 |
+ // FIXME: Now mount the layers |
|
| 86 |
+ rwBranch := fmt.Sprintf("%v=rw", rw)
|
|
| 87 |
+ roBranches := "" |
|
| 88 |
+ for _, layer := range ro {
|
|
| 89 |
+ roBranches += fmt.Sprintf("%v=ro:", layer)
|
|
| 90 |
+ } |
|
| 91 |
+ branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
|
|
| 92 |
+ return mount("none", target, "aufs", 0, branches)
|
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+func (image *Image) Mount(root, rw string) error {
|
|
| 96 |
+ if mounted, err := Mounted(root); err != nil {
|
|
| 97 |
+ return err |
|
| 98 |
+ } else if mounted {
|
|
| 99 |
+ return fmt.Errorf("%s is already mounted", root)
|
|
| 100 |
+ } |
|
| 101 |
+ layers, err := image.layers() |
|
| 102 |
+ if err != nil {
|
|
| 103 |
+ return err |
|
| 104 |
+ } |
|
| 105 |
+ // Create the target directories if they don't exist |
|
| 106 |
+ if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) {
|
|
| 107 |
+ return err |
|
| 108 |
+ } |
|
| 109 |
+ if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {
|
|
| 110 |
+ return err |
|
| 111 |
+ } |
|
| 112 |
+ // FIXME: @creack shouldn't we do this after going over changes? |
|
| 113 |
+ if err := MountAUFS(layers, rw, root); err != nil {
|
|
| 114 |
+ return err |
|
| 115 |
+ } |
|
| 116 |
+ // FIXME: Create tests for deletion |
|
| 117 |
+ // FIXME: move this part to change.go |
|
| 118 |
+ // Retrieve the changeset from the parent and apply it to the container |
|
| 119 |
+ // - Retrieve the changes |
|
| 120 |
+ changes, err := Changes(layers, layers[0]) |
|
| 121 |
+ if err != nil {
|
|
| 122 |
+ return err |
|
| 123 |
+ } |
|
| 124 |
+ // Iterate on changes |
|
| 125 |
+ for _, c := range changes {
|
|
| 126 |
+ // If there is a delete |
|
| 127 |
+ if c.Kind == ChangeDelete {
|
|
| 128 |
+ // Make sure the directory exists |
|
| 129 |
+ file_path, file_name := path.Dir(c.Path), path.Base(c.Path) |
|
| 130 |
+ if err := os.MkdirAll(path.Join(rw, file_path), 0755); err != nil {
|
|
| 131 |
+ return err |
|
| 132 |
+ } |
|
| 133 |
+ // And create the whiteout (we just need to create empty file, discard the return) |
|
| 134 |
+ if _, err := os.Create(path.Join(path.Join(rw, file_path), |
|
| 135 |
+ ".wh."+path.Base(file_name))); err != nil {
|
|
| 136 |
+ return err |
|
| 137 |
+ } |
|
| 138 |
+ } |
|
| 139 |
+ } |
|
| 140 |
+ return nil |
|
| 141 |
+} |
|
| 142 |
+ |
|
| 143 |
+func (image *Image) Changes(rw string) ([]Change, error) {
|
|
| 144 |
+ layers, err := image.layers() |
|
| 145 |
+ if err != nil {
|
|
| 146 |
+ return nil, err |
|
| 147 |
+ } |
|
| 148 |
+ return Changes(layers, rw) |
|
| 149 |
+} |
|
| 150 |
+ |
|
| 151 |
+func ValidateId(id string) error {
|
|
| 152 |
+ if id == "" {
|
|
| 153 |
+ return fmt.Errorf("Image id can't be empty")
|
|
| 154 |
+ } |
|
| 155 |
+ if strings.Contains(id, ":") {
|
|
| 156 |
+ return fmt.Errorf("Invalid character in image id: ':'")
|
|
| 157 |
+ } |
|
| 158 |
+ return nil |
|
| 159 |
+} |
|
| 160 |
+ |
|
| 161 |
+func GenerateId() string {
|
|
| 162 |
+ // FIXME: don't seed every time |
|
| 163 |
+ rand.Seed(time.Now().UTC().UnixNano()) |
|
| 164 |
+ randomBytes := bytes.NewBuffer([]byte(fmt.Sprintf("%x", rand.Int())))
|
|
| 165 |
+ id, _ := ComputeId(randomBytes) // can't fail |
|
| 166 |
+ return id |
|
| 167 |
+} |
|
| 168 |
+ |
|
| 169 |
+// ComputeId reads from `content` until EOF, then returns a SHA of what it read, as a string. |
|
| 170 |
+func ComputeId(content io.Reader) (string, error) {
|
|
| 171 |
+ h := sha256.New() |
|
| 172 |
+ if _, err := io.Copy(h, content); err != nil {
|
|
| 173 |
+ return "", err |
|
| 174 |
+ } |
|
| 175 |
+ return fmt.Sprintf("%x", h.Sum(nil)[:8]), nil
|
|
| 176 |
+} |
|
| 177 |
+ |
|
| 178 |
+// Image includes convenience proxy functions to its graph |
|
| 179 |
+// These functions will return an error if the image is not registered |
|
| 180 |
+// (ie. if image.graph == nil) |
|
| 181 |
+ |
|
| 182 |
+func (img *Image) History() ([]*Image, error) {
|
|
| 183 |
+ var parents []*Image |
|
| 184 |
+ if err := img.WalkHistory( |
|
| 185 |
+ func(img *Image) {
|
|
| 186 |
+ parents = append(parents, img) |
|
| 187 |
+ }, |
|
| 188 |
+ ); err != nil {
|
|
| 189 |
+ return nil, err |
|
| 190 |
+ } |
|
| 191 |
+ return parents, nil |
|
| 192 |
+} |
|
| 193 |
+ |
|
| 194 |
+// layers returns all the filesystem layers needed to mount an image |
|
| 195 |
+func (img *Image) layers() ([]string, error) {
|
|
| 196 |
+ var list []string |
|
| 197 |
+ var e error |
|
| 198 |
+ if err := img.WalkHistory( |
|
| 199 |
+ func(img *Image) {
|
|
| 200 |
+ if layer, err := img.layer(); err != nil {
|
|
| 201 |
+ e = err |
|
| 202 |
+ } else if layer != "" {
|
|
| 203 |
+ list = append(list, layer) |
|
| 204 |
+ } |
|
| 205 |
+ }, |
|
| 206 |
+ ); err != nil {
|
|
| 207 |
+ return nil, err |
|
| 208 |
+ } else if e != nil { // Did an error occur inside the handler?
|
|
| 209 |
+ return nil, e |
|
| 210 |
+ } |
|
| 211 |
+ if len(list) == 0 {
|
|
| 212 |
+ return nil, fmt.Errorf("No layer found for image %s\n", img.Id)
|
|
| 213 |
+ } |
|
| 214 |
+ return list, nil |
|
| 215 |
+} |
|
| 216 |
+ |
|
| 217 |
+func (img *Image) WalkHistory(handler func(*Image)) error {
|
|
| 218 |
+ var err error |
|
| 219 |
+ currentImg := img |
|
| 220 |
+ for currentImg != nil {
|
|
| 221 |
+ if handler != nil {
|
|
| 222 |
+ handler(currentImg) |
|
| 223 |
+ } |
|
| 224 |
+ currentImg, err = currentImg.GetParent() |
|
| 225 |
+ if err != nil {
|
|
| 226 |
+ return fmt.Errorf("Error while getting parent image: %v", err)
|
|
| 227 |
+ } |
|
| 228 |
+ } |
|
| 229 |
+ return nil |
|
| 230 |
+} |
|
| 231 |
+ |
|
| 232 |
+func (img *Image) GetParent() (*Image, error) {
|
|
| 233 |
+ if img.Parent == "" {
|
|
| 234 |
+ return nil, nil |
|
| 235 |
+ } |
|
| 236 |
+ if img.graph == nil {
|
|
| 237 |
+ return nil, fmt.Errorf("Can't lookup parent of unregistered image")
|
|
| 238 |
+ } |
|
| 239 |
+ return img.graph.Get(img.Parent) |
|
| 240 |
+} |
|
| 241 |
+ |
|
| 242 |
+func (img *Image) root() (string, error) {
|
|
| 243 |
+ if img.graph == nil {
|
|
| 244 |
+ return "", fmt.Errorf("Can't lookup root of unregistered image")
|
|
| 245 |
+ } |
|
| 246 |
+ return img.graph.imageRoot(img.Id), nil |
|
| 247 |
+} |
|
| 248 |
+ |
|
| 249 |
+// Return the path of an image's layer |
|
| 250 |
+func (img *Image) layer() (string, error) {
|
|
| 251 |
+ root, err := img.root() |
|
| 252 |
+ if err != nil {
|
|
| 253 |
+ return "", err |
|
| 254 |
+ } |
|
| 255 |
+ return layerPath(root), nil |
|
| 256 |
+} |
| 0 | 257 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,48 @@ |
| 0 |
+package docker |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "os" |
|
| 5 |
+ "path/filepath" |
|
| 6 |
+ "syscall" |
|
| 7 |
+ "time" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func Unmount(target string) error {
|
|
| 11 |
+ if err := syscall.Unmount(target, 0); err != nil {
|
|
| 12 |
+ return err |
|
| 13 |
+ } |
|
| 14 |
+ // Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint |
|
| 15 |
+ // for some time. We'll just keep retrying until it succeeds. |
|
| 16 |
+ for retries := 0; retries < 1000; retries++ {
|
|
| 17 |
+ err := os.Remove(target) |
|
| 18 |
+ if err == nil {
|
|
| 19 |
+ // rm mntpoint succeeded |
|
| 20 |
+ return nil |
|
| 21 |
+ } |
|
| 22 |
+ if os.IsNotExist(err) {
|
|
| 23 |
+ // mntpoint doesn't exist anymore. Success. |
|
| 24 |
+ return nil |
|
| 25 |
+ } |
|
| 26 |
+ // fmt.Printf("(%v) Remove %v returned: %v\n", retries, target, err)
|
|
| 27 |
+ time.Sleep(10 * time.Millisecond) |
|
| 28 |
+ } |
|
| 29 |
+ return fmt.Errorf("Umount: Failed to umount %v", target)
|
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+func Mounted(mountpoint string) (bool, error) {
|
|
| 33 |
+ mntpoint, err := os.Stat(mountpoint) |
|
| 34 |
+ if err != nil {
|
|
| 35 |
+ if os.IsNotExist(err) {
|
|
| 36 |
+ return false, nil |
|
| 37 |
+ } |
|
| 38 |
+ return false, err |
|
| 39 |
+ } |
|
| 40 |
+ parent, err := os.Stat(filepath.Join(mountpoint, "..")) |
|
| 41 |
+ if err != nil {
|
|
| 42 |
+ return false, err |
|
| 43 |
+ } |
|
| 44 |
+ mntpointSt := mntpoint.Sys().(*syscall.Stat_t) |
|
| 45 |
+ parentSt := parent.Sys().(*syscall.Stat_t) |
|
| 46 |
+ return mntpointSt.Dev != parentSt.Dev, nil |
|
| 47 |
+} |
| ... | ... |
@@ -3,7 +3,6 @@ package docker |
| 3 | 3 |
import ( |
| 4 | 4 |
"container/list" |
| 5 | 5 |
"fmt" |
| 6 |
- "github.com/dotcloud/docker/graph" |
|
| 7 | 6 |
"io" |
| 8 | 7 |
"io/ioutil" |
| 9 | 8 |
"log" |
| ... | ... |
@@ -19,8 +18,8 @@ type Runtime struct {
|
| 19 | 19 |
repository string |
| 20 | 20 |
containers *list.List |
| 21 | 21 |
networkManager *NetworkManager |
| 22 |
- graph *graph.Graph |
|
| 23 |
- repositories *graph.TagStore |
|
| 22 |
+ graph *Graph |
|
| 23 |
+ repositories *TagStore |
|
| 24 | 24 |
} |
| 25 | 25 |
|
| 26 | 26 |
var sysInitPath string |
| ... | ... |
@@ -191,22 +190,22 @@ func (runtime *Runtime) restore() error {
|
| 191 | 191 |
return nil |
| 192 | 192 |
} |
| 193 | 193 |
|
| 194 |
-func New() (*Runtime, error) {
|
|
| 195 |
- return NewFromDirectory("/var/lib/docker")
|
|
| 194 |
+func NewRuntime() (*Runtime, error) {
|
|
| 195 |
+ return NewRuntimeFromDirectory("/var/lib/docker")
|
|
| 196 | 196 |
} |
| 197 | 197 |
|
| 198 |
-func NewFromDirectory(root string) (*Runtime, error) {
|
|
| 198 |
+func NewRuntimeFromDirectory(root string) (*Runtime, error) {
|
|
| 199 | 199 |
runtime_repo := path.Join(root, "containers") |
| 200 | 200 |
|
| 201 | 201 |
if err := os.MkdirAll(runtime_repo, 0700); err != nil && !os.IsExist(err) {
|
| 202 | 202 |
return nil, err |
| 203 | 203 |
} |
| 204 | 204 |
|
| 205 |
- g, err := graph.New(path.Join(root, "graph")) |
|
| 205 |
+ g, err := NewGraph(path.Join(root, "graph")) |
|
| 206 | 206 |
if err != nil {
|
| 207 | 207 |
return nil, err |
| 208 | 208 |
} |
| 209 |
- repositories, err := graph.NewTagStore(path.Join(root, "repositories"), g) |
|
| 209 |
+ repositories, err := NewTagStore(path.Join(root, "repositories"), g) |
|
| 210 | 210 |
if err != nil {
|
| 211 | 211 |
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
|
| 212 | 212 |
} |
| ... | ... |
@@ -1,7 +1,6 @@ |
| 1 | 1 |
package docker |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "github.com/dotcloud/docker/graph" |
|
| 5 | 4 |
"io" |
| 6 | 5 |
"io/ioutil" |
| 7 | 6 |
"os" |
| ... | ... |
@@ -57,7 +56,7 @@ func init() {
|
| 57 | 57 |
unitTestStoreBase = root |
| 58 | 58 |
|
| 59 | 59 |
// Make it our Store root |
| 60 |
- runtime, err := NewFromDirectory(root) |
|
| 60 |
+ runtime, err := NewRuntimeFromDirectory(root) |
|
| 61 | 61 |
if err != nil {
|
| 62 | 62 |
panic(err) |
| 63 | 63 |
} |
| ... | ... |
@@ -84,7 +83,7 @@ func newTestRuntime() (*Runtime, error) {
|
| 84 | 84 |
return nil, err |
| 85 | 85 |
} |
| 86 | 86 |
|
| 87 |
- runtime, err := NewFromDirectory(root) |
|
| 87 |
+ runtime, err := NewRuntimeFromDirectory(root) |
|
| 88 | 88 |
if err != nil {
|
| 89 | 89 |
return nil, err |
| 90 | 90 |
} |
| ... | ... |
@@ -92,7 +91,7 @@ func newTestRuntime() (*Runtime, error) {
|
| 92 | 92 |
return runtime, nil |
| 93 | 93 |
} |
| 94 | 94 |
|
| 95 |
-func GetTestImage(runtime *Runtime) *graph.Image {
|
|
| 95 |
+func GetTestImage(runtime *Runtime) *Image {
|
|
| 96 | 96 |
imgs, err := runtime.graph.All() |
| 97 | 97 |
if err != nil {
|
| 98 | 98 |
panic(err) |
| ... | ... |
@@ -102,7 +101,7 @@ func GetTestImage(runtime *Runtime) *graph.Image {
|
| 102 | 102 |
return imgs[0] |
| 103 | 103 |
} |
| 104 | 104 |
|
| 105 |
-func TestCreate(t *testing.T) {
|
|
| 105 |
+func TestRuntimeCreate(t *testing.T) {
|
|
| 106 | 106 |
runtime, err := newTestRuntime() |
| 107 | 107 |
if err != nil {
|
| 108 | 108 |
t.Fatal(err) |
| ... | ... |
@@ -269,7 +268,7 @@ func TestRestore(t *testing.T) {
|
| 269 | 269 |
t.Fatal(err) |
| 270 | 270 |
} |
| 271 | 271 |
|
| 272 |
- runtime1, err := NewFromDirectory(root) |
|
| 272 |
+ runtime1, err := NewRuntimeFromDirectory(root) |
|
| 273 | 273 |
if err != nil {
|
| 274 | 274 |
t.Fatal(err) |
| 275 | 275 |
} |
| ... | ... |
@@ -294,7 +293,7 @@ func TestRestore(t *testing.T) {
|
| 294 | 294 |
|
| 295 | 295 |
// Here are are simulating a docker restart - that is, reloading all containers |
| 296 | 296 |
// from scratch |
| 297 |
- runtime2, err := NewFromDirectory(root) |
|
| 297 |
+ runtime2, err := NewRuntimeFromDirectory(root) |
|
| 298 | 298 |
if err != nil {
|
| 299 | 299 |
t.Fatal(err) |
| 300 | 300 |
} |
| 301 | 301 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,92 @@ |
| 0 |
+package docker |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "path/filepath" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+type TagStore struct {
|
|
| 9 |
+ path string |
|
| 10 |
+ graph *Graph |
|
| 11 |
+ Repositories map[string]Repository |
|
| 12 |
+} |
|
| 13 |
+ |
|
| 14 |
+type Repository map[string]string |
|
| 15 |
+ |
|
| 16 |
+func NewTagStore(path string, graph *Graph) (*TagStore, error) {
|
|
| 17 |
+ abspath, err := filepath.Abs(path) |
|
| 18 |
+ if err != nil {
|
|
| 19 |
+ return nil, err |
|
| 20 |
+ } |
|
| 21 |
+ store := &TagStore{
|
|
| 22 |
+ path: abspath, |
|
| 23 |
+ graph: graph, |
|
| 24 |
+ Repositories: make(map[string]Repository), |
|
| 25 |
+ } |
|
| 26 |
+ if err := store.Save(); err != nil {
|
|
| 27 |
+ return nil, err |
|
| 28 |
+ } |
|
| 29 |
+ return store, nil |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+func (store *TagStore) Save() error {
|
|
| 33 |
+ // Store the json ball |
|
| 34 |
+ jsonData, err := json.Marshal(store) |
|
| 35 |
+ if err != nil {
|
|
| 36 |
+ return err |
|
| 37 |
+ } |
|
| 38 |
+ if err := ioutil.WriteFile(store.path, jsonData, 0600); err != nil {
|
|
| 39 |
+ return err |
|
| 40 |
+ } |
|
| 41 |
+ return nil |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+func (store *TagStore) Reload() error {
|
|
| 45 |
+ jsonData, err := ioutil.ReadFile(store.path) |
|
| 46 |
+ if err != nil {
|
|
| 47 |
+ return err |
|
| 48 |
+ } |
|
| 49 |
+ if err := json.Unmarshal(jsonData, store); err != nil {
|
|
| 50 |
+ return err |
|
| 51 |
+ } |
|
| 52 |
+ return nil |
|
| 53 |
+} |
|
| 54 |
+ |
|
| 55 |
+func (store *TagStore) Set(repoName, tag, revision string) error {
|
|
| 56 |
+ if err := store.Reload(); err != nil {
|
|
| 57 |
+ return err |
|
| 58 |
+ } |
|
| 59 |
+ var repo Repository |
|
| 60 |
+ if r, exists := store.Repositories[repoName]; exists {
|
|
| 61 |
+ repo = r |
|
| 62 |
+ } else {
|
|
| 63 |
+ repo = make(map[string]string) |
|
| 64 |
+ store.Repositories[repoName] = repo |
|
| 65 |
+ } |
|
| 66 |
+ repo[tag] = revision |
|
| 67 |
+ return store.Save() |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 70 |
+func (store *TagStore) Get(repoName string) (Repository, error) {
|
|
| 71 |
+ if err := store.Reload(); err != nil {
|
|
| 72 |
+ return nil, err |
|
| 73 |
+ } |
|
| 74 |
+ if r, exists := store.Repositories[repoName]; exists {
|
|
| 75 |
+ return r, nil |
|
| 76 |
+ } |
|
| 77 |
+ return nil, nil |
|
| 78 |
+} |
|
| 79 |
+ |
|
| 80 |
+func (store *TagStore) GetImage(repoName, tag string) (*Image, error) {
|
|
| 81 |
+ repo, err := store.Get(repoName) |
|
| 82 |
+ if err != nil {
|
|
| 83 |
+ return nil, err |
|
| 84 |
+ } else if repo == nil {
|
|
| 85 |
+ return nil, nil |
|
| 86 |
+ } |
|
| 87 |
+ if revision, exists := repo[tag]; exists {
|
|
| 88 |
+ return store.graph.Get(revision) |
|
| 89 |
+ } |
|
| 90 |
+ return nil, nil |
|
| 91 |
+} |