Browse code

Folded graph/ back into main package

Solomon Hykes authored on 2013/03/22 09:47:23
Showing 24 changed files
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
+}
... ...
@@ -803,7 +803,7 @@ func NewServer() (*Server, error) {
803 803
 	// if err != nil {
804 804
 	// 	return nil, err
805 805
 	// }
806
-	runtime, err := New()
806
+	runtime, err := NewRuntime()
807 807
 	if err != nil {
808 808
 		return nil, err
809 809
 	}
... ...
@@ -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
-}
49 1
deleted file mode 100644
... ...
@@ -1,7 +0,0 @@
1
-package graph
2
-
3
-import "errors"
4
-
5
-func mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
6
-	return errors.New("mount is not implemented on darwin")
7
-}
8 1
deleted file mode 100644
... ...
@@ -1,7 +0,0 @@
1
-package graph
2
-
3
-import "syscall"
4
-
5
-func mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
6
-	return syscall.Mount(source, target, fstype, flags, data)
7
-}
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
+}
0 48
new file mode 100644
... ...
@@ -0,0 +1,7 @@
0
+package docker
1
+
2
+import "errors"
3
+
4
+func mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
5
+	return errors.New("mount is not implemented on darwin")
6
+}
0 7
new file mode 100644
... ...
@@ -0,0 +1,7 @@
0
+package docker
1
+
2
+import "syscall"
3
+
4
+func mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
5
+	return syscall.Mount(source, target, fstype, flags, data)
6
+}
... ...
@@ -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
+}