|
@@ -0,0 +1,245 @@
|
|
|
|
+package graph
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "encoding/json"
|
|
|
|
+ "fmt"
|
|
|
|
+ "github.com/dotcloud/docker/future"
|
|
|
|
+ "io/ioutil"
|
|
|
|
+ "os"
|
|
|
|
+ "path"
|
|
|
|
+ "strings"
|
|
|
|
+ "syscall"
|
|
|
|
+ "time"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+type Image struct {
|
|
|
|
+ Id string
|
|
|
|
+ Parent string
|
|
|
|
+ Comment string
|
|
|
|
+ Created time.Time
|
|
|
|
+ graph *Graph
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func LoadImage(root string) (*Image, error) {
|
|
|
|
+ // Load the json data
|
|
|
|
+ jsonData, err := ioutil.ReadFile(jsonPath(root))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ var img Image
|
|
|
|
+ if err := json.Unmarshal(jsonData, &img); err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ if err := ValidateId(img.Id); err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ // Check that the filesystem layer exists
|
|
|
|
+ if stat, err := os.Stat(layerPath(root)); err != nil {
|
|
|
|
+ if os.IsNotExist(err) {
|
|
|
|
+ return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.Id)
|
|
|
|
+ } else {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ } else if !stat.IsDir() {
|
|
|
|
+ return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.Id, layerPath(root))
|
|
|
|
+ }
|
|
|
|
+ return &img, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func StoreImage(img *Image, layerData Archive, root string) error {
|
|
|
|
+ // Check that root doesn't already exist
|
|
|
|
+ if _, err := os.Stat(root); err == nil {
|
|
|
|
+ return fmt.Errorf("Image %s already exists", img.Id)
|
|
|
|
+ } else if !os.IsNotExist(err) {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ // Store the layer
|
|
|
|
+ layer := layerPath(root)
|
|
|
|
+ if err := os.MkdirAll(layer, 0700); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ if err := Untar(layerData, layer); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ // Store the json ball
|
|
|
|
+ jsonData, err := json.Marshal(img)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func layerPath(root string) string {
|
|
|
|
+ return path.Join(root, "layer")
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func jsonPath(root string) string {
|
|
|
|
+ return path.Join(root, "json")
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func MountAUFS(ro []string, rw string, target string) error {
|
|
|
|
+ // FIXME: Now mount the layers
|
|
|
|
+ rwBranch := fmt.Sprintf("%v=rw", rw)
|
|
|
|
+ roBranches := ""
|
|
|
|
+ for _, layer := range ro {
|
|
|
|
+ roBranches += fmt.Sprintf("%v=ro:", layer)
|
|
|
|
+ }
|
|
|
|
+ branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
|
|
|
|
+ return mount("none", target, "aufs", 0, branches)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func Unmount(target string) error {
|
|
|
|
+ if err := syscall.Unmount(target, 0); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ // Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint
|
|
|
|
+ // for some time. We'll just keep retrying until it succeeds.
|
|
|
|
+ for retries := 0; retries < 1000; retries++ {
|
|
|
|
+ err := os.Remove(target)
|
|
|
|
+ if err == nil {
|
|
|
|
+ // rm mntpoint succeeded
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+ if os.IsNotExist(err) {
|
|
|
|
+ // mntpoint doesn't exist anymore. Success.
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+ // fmt.Printf("(%v) Remove %v returned: %v\n", retries, target, err)
|
|
|
|
+ time.Sleep(10 * time.Millisecond)
|
|
|
|
+ }
|
|
|
|
+ return fmt.Errorf("Umount: Failed to umount %v", target)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (image *Image) Mount(root, rw string) error {
|
|
|
|
+ layers, err := image.layers()
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ // FIXME: @creack shouldn't we do this after going over changes?
|
|
|
|
+ if err := MountAUFS(layers, rw, root); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ // FIXME: Create tests for deletion
|
|
|
|
+ // FIXME: move this part to change.go
|
|
|
|
+ // Retrieve the changeset from the parent and apply it to the container
|
|
|
|
+ // - Retrieve the changes
|
|
|
|
+ changes, err := Changes(layers, layers[0])
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ // Iterate on changes
|
|
|
|
+ for _, c := range changes {
|
|
|
|
+ // If there is a delete
|
|
|
|
+ if c.Kind == ChangeDelete {
|
|
|
|
+ // Make sure the directory exists
|
|
|
|
+ file_path, file_name := path.Dir(c.Path), path.Base(c.Path)
|
|
|
|
+ if err := os.MkdirAll(path.Join(rw, file_path), 0755); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ // And create the whiteout (we just need to create empty file, discard the return)
|
|
|
|
+ if _, err := os.Create(path.Join(path.Join(rw, file_path),
|
|
|
|
+ ".wh."+path.Base(file_name))); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func ValidateId(id string) error {
|
|
|
|
+ if id == "" {
|
|
|
|
+ return fmt.Errorf("Image id can't be empty")
|
|
|
|
+ }
|
|
|
|
+ if strings.Contains(id, ":") {
|
|
|
|
+ return fmt.Errorf("Invalid character in image id: ':'")
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func GenerateId() string {
|
|
|
|
+ future.Seed()
|
|
|
|
+ return future.RandomId()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Image includes convenience proxy functions to its graph
|
|
|
|
+// These functions will return an error if the image is not registered
|
|
|
|
+// (ie. if image.graph == nil)
|
|
|
|
+
|
|
|
|
+func (img *Image) History() ([]*Image, error) {
|
|
|
|
+ var parents []*Image
|
|
|
|
+ if err := img.WalkHistory(
|
|
|
|
+ func(img *Image) {
|
|
|
|
+ parents = append(parents, img)
|
|
|
|
+ },
|
|
|
|
+ ); err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ return parents, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// layers returns all the filesystem layers needed to mount an image
|
|
|
|
+func (img *Image) layers() ([]string, error) {
|
|
|
|
+ var list []string
|
|
|
|
+ var e error
|
|
|
|
+ if err := img.WalkHistory(
|
|
|
|
+ func(img *Image) {
|
|
|
|
+ if layer, err := img.layer(); err != nil {
|
|
|
|
+ e = err
|
|
|
|
+ } else if layer != "" {
|
|
|
|
+ list = append(list, layer)
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ ); err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ } else if e != nil { // Did an error occur inside the handler?
|
|
|
|
+ return nil, e
|
|
|
|
+ }
|
|
|
|
+ if len(list) == 0 {
|
|
|
|
+ return nil, fmt.Errorf("No layer found for image %s\n", img.Id)
|
|
|
|
+ }
|
|
|
|
+ return list, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (img *Image) WalkHistory(handler func(*Image)) error {
|
|
|
|
+ var err error
|
|
|
|
+ currentImg := img
|
|
|
|
+ for currentImg != nil {
|
|
|
|
+ if handler != nil {
|
|
|
|
+ handler(currentImg)
|
|
|
|
+ }
|
|
|
|
+ currentImg, err = currentImg.GetParent()
|
|
|
|
+ if err != nil {
|
|
|
|
+ return fmt.Errorf("Error while getting parent image: %v", err)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (img *Image) GetParent() (*Image, error) {
|
|
|
|
+ if img.Parent == "" {
|
|
|
|
+ return nil, nil
|
|
|
|
+ }
|
|
|
|
+ if img.graph == nil {
|
|
|
|
+ return nil, fmt.Errorf("Can't lookup parent of unregistered image")
|
|
|
|
+ }
|
|
|
|
+ return img.graph.Get(img.Parent)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (img *Image) root() (string, error) {
|
|
|
|
+ if img.graph == nil {
|
|
|
|
+ return "", fmt.Errorf("Can't lookup root of unregistered image")
|
|
|
|
+ }
|
|
|
|
+ return img.graph.imageRoot(img.Id), nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Return the path of an image's layer
|
|
|
|
+func (img *Image) layer() (string, error) {
|
|
|
|
+ root, err := img.root()
|
|
|
|
+ if err != nil {
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+ return layerPath(root), nil
|
|
|
|
+}
|