package docker import ( "bytes" "crypto/sha256" "encoding/json" "fmt" "io" "io/ioutil" "math/rand" "os" "path" "strings" "time" ) type Image struct { Id string `json:"id"` Parent string `json:"parent,omitempty"` Comment string `json:"comment,omitempty"` Created time.Time `json:"created"` Container string `json:"container,omitempty"` ContainerConfig Config `json:"container_config,omitempty"` 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 (image *Image) Mount(root, rw string) error { if mounted, err := Mounted(root); err != nil { return err } else if mounted { return fmt.Errorf("%s is already mounted", root) } layers, err := image.layers() if err != nil { return err } // Create the target directories if they don't exist if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) { return err } if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { 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 (image *Image) Changes(rw string) ([]Change, error) { layers, err := image.layers() if err != nil { return nil, err } return Changes(layers, rw) } 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 { // FIXME: don't seed every time rand.Seed(time.Now().UTC().UnixNano()) randomBytes := bytes.NewBuffer([]byte(fmt.Sprintf("%x", rand.Int()))) id, _ := ComputeId(randomBytes) // can't fail return id } // ComputeId reads from `content` until EOF, then returns a SHA of what it read, as a string. func ComputeId(content io.Reader) (string, error) { h := sha256.New() if _, err := io.Copy(h, content); err != nil { return "", err } return fmt.Sprintf("%x", h.Sum(nil)[:8]), nil } // 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) error { parents = append(parents, img) return nil }, ); err != nil { return nil, err } return parents, nil } // layers returns all the filesystem layers needed to mount an image // FIXME: @shykes refactor this function with the new error handling // (I'll do it if I have time tonight, I focus on the rest) func (img *Image) layers() ([]string, error) { var list []string var e error if err := img.WalkHistory( func(img *Image) (err error) { if layer, err := img.layer(); err != nil { e = err } else if layer != "" { list = append(list, layer) } return err }, ); 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) (err error) { currentImg := img for currentImg != nil { if handler != nil { if err := handler(currentImg); err != nil { return err } } 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 }