Merge branch 'graph' of github.com:dotcloud/docker into graph
Upstream-commit: 864a8d9acaf306f10ddb50c2bd2398ebd54f0042 Component: engine
This commit is contained in:
257
components/engine/image.go
Normal file
257
components/engine/image.go
Normal file
@ -0,0 +1,257 @@
|
||||
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"`
|
||||
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) {
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user