2013-03-21 17:47:23 -07:00
|
|
|
package docker
|
2013-03-18 00:15:35 -07:00
|
|
|
|
|
|
|
import (
|
2013-03-26 16:46:27 -04:00
|
|
|
"crypto/rand"
|
|
|
|
"encoding/hex"
|
2013-03-18 00:15:35 -07:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2013-05-14 22:37:35 +00:00
|
|
|
"github.com/dotcloud/docker/utils"
|
2013-03-21 01:07:07 -07:00
|
|
|
"io"
|
2013-03-18 00:15:35 -07:00
|
|
|
"io/ioutil"
|
2013-04-15 12:05:46 +02:00
|
|
|
"log"
|
2013-03-18 00:15:35 -07:00
|
|
|
"os"
|
2013-04-15 12:05:46 +02:00
|
|
|
"os/exec"
|
2013-03-18 00:15:35 -07:00
|
|
|
"path"
|
2013-05-13 15:10:26 +02:00
|
|
|
"path/filepath"
|
2013-07-22 15:40:33 -07:00
|
|
|
"strconv"
|
2013-03-18 00:15:35 -07:00
|
|
|
"strings"
|
2013-09-04 12:55:48 +02:00
|
|
|
"syscall"
|
2013-03-18 00:15:35 -07:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Image struct {
|
2013-06-04 18:00:22 +00:00
|
|
|
ID string `json:"id"`
|
2013-03-21 06:35:57 -07:00
|
|
|
Parent string `json:"parent,omitempty"`
|
|
|
|
Comment string `json:"comment,omitempty"`
|
|
|
|
Created time.Time `json:"created"`
|
2013-03-23 14:48:16 -07:00
|
|
|
Container string `json:"container,omitempty"`
|
|
|
|
ContainerConfig Config `json:"container_config,omitempty"`
|
2013-04-04 18:38:43 -07:00
|
|
|
DockerVersion string `json:"docker_version,omitempty"`
|
2013-04-17 19:58:17 -07:00
|
|
|
Author string `json:"author,omitempty"`
|
2013-04-25 16:48:31 -07:00
|
|
|
Config *Config `json:"config,omitempty"`
|
2013-05-24 21:40:56 +03:00
|
|
|
Architecture string `json:"architecture,omitempty"`
|
2013-03-21 21:13:27 -07:00
|
|
|
graph *Graph
|
2013-05-13 15:10:26 +02:00
|
|
|
Size int64
|
2013-03-18 00:15:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func LoadImage(root string) (*Image, error) {
|
|
|
|
// Load the json data
|
|
|
|
jsonData, err := ioutil.ReadFile(jsonPath(root))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2013-05-08 10:42:29 -07:00
|
|
|
img := &Image{}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(jsonData, img); err != nil {
|
2013-03-18 00:15:35 -07:00
|
|
|
return nil, err
|
|
|
|
}
|
2013-06-04 18:00:22 +00:00
|
|
|
if err := ValidateID(img.ID); err != nil {
|
2013-03-18 00:15:35 -07:00
|
|
|
return nil, err
|
|
|
|
}
|
2013-07-22 15:40:33 -07:00
|
|
|
|
|
|
|
if buf, err := ioutil.ReadFile(path.Join(root, "layersize")); err != nil {
|
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if size, err := strconv.Atoi(string(buf)); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
img.Size = int64(size)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-18 00:15:35 -07:00
|
|
|
// Check that the filesystem layer exists
|
|
|
|
if stat, err := os.Stat(layerPath(root)); err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
2013-06-04 18:00:22 +00:00
|
|
|
return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.ID)
|
2013-03-18 00:15:35 -07:00
|
|
|
}
|
2013-06-04 13:51:12 +00:00
|
|
|
return nil, err
|
2013-03-18 00:15:35 -07:00
|
|
|
} else if !stat.IsDir() {
|
2013-06-04 18:00:22 +00:00
|
|
|
return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.ID, layerPath(root))
|
2013-03-18 00:15:35 -07:00
|
|
|
}
|
2013-05-08 10:42:29 -07:00
|
|
|
return img, nil
|
2013-03-18 00:15:35 -07:00
|
|
|
}
|
|
|
|
|
2013-07-22 15:44:55 -07:00
|
|
|
func StoreImage(img *Image, jsonData []byte, layerData Archive, root string) error {
|
2013-03-18 00:15:35 -07:00
|
|
|
// Check that root doesn't already exist
|
|
|
|
if _, err := os.Stat(root); err == nil {
|
2013-06-04 18:00:22 +00:00
|
|
|
return fmt.Errorf("Image %s already exists", img.ID)
|
2013-03-18 00:15:35 -07:00
|
|
|
} else if !os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Store the layer
|
|
|
|
layer := layerPath(root)
|
2013-07-25 15:45:15 +00:00
|
|
|
if err := os.MkdirAll(layer, 0755); err != nil {
|
2013-03-18 00:15:35 -07:00
|
|
|
return err
|
|
|
|
}
|
2013-05-08 19:08:11 -07:00
|
|
|
|
2013-06-22 12:29:42 -07:00
|
|
|
// If layerData is not nil, unpack it into the new layer
|
|
|
|
if layerData != nil {
|
2013-07-08 13:30:03 -07:00
|
|
|
start := time.Now()
|
|
|
|
utils.Debugf("Start untar layer")
|
2013-06-22 12:29:42 -07:00
|
|
|
if err := Untar(layerData, layer); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-07-08 13:30:03 -07:00
|
|
|
utils.Debugf("Untar time: %vs\n", time.Now().Sub(start).Seconds())
|
2013-03-18 00:15:35 -07:00
|
|
|
}
|
2013-05-13 15:10:26 +02:00
|
|
|
|
2013-07-22 15:40:33 -07:00
|
|
|
// If raw json is provided, then use it
|
|
|
|
if jsonData != nil {
|
|
|
|
return ioutil.WriteFile(jsonPath(root), jsonData, 0600)
|
|
|
|
} else { // Otherwise, unmarshal the image
|
|
|
|
jsonData, err := json.Marshal(img)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-13 15:10:26 +02:00
|
|
|
return StoreSize(img, root)
|
|
|
|
}
|
|
|
|
|
|
|
|
func StoreSize(img *Image, root string) error {
|
|
|
|
layer := layerPath(root)
|
|
|
|
|
2013-07-22 15:40:33 -07:00
|
|
|
var totalSize int64 = 0
|
2013-05-13 15:10:26 +02:00
|
|
|
filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error {
|
2013-07-22 15:40:33 -07:00
|
|
|
totalSize += fileInfo.Size()
|
2013-05-13 15:10:26 +02:00
|
|
|
return nil
|
|
|
|
})
|
2013-07-22 15:40:33 -07:00
|
|
|
img.Size = totalSize
|
2013-05-13 15:10:26 +02:00
|
|
|
|
2013-07-22 15:40:33 -07:00
|
|
|
if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(totalSize))), 0600); err != nil {
|
|
|
|
return nil
|
2013-03-18 00:15:35 -07:00
|
|
|
}
|
2013-07-22 15:40:33 -07:00
|
|
|
|
2013-03-18 00:15:35 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func layerPath(root string) string {
|
|
|
|
return path.Join(root, "layer")
|
|
|
|
}
|
|
|
|
|
|
|
|
func jsonPath(root string) string {
|
|
|
|
return path.Join(root, "json")
|
|
|
|
}
|
|
|
|
|
2013-09-04 12:55:48 +02:00
|
|
|
func mountPath(root string) string {
|
|
|
|
return path.Join(root, "mount")
|
|
|
|
}
|
|
|
|
|
2013-03-18 00:15:35 -07:00
|
|
|
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 {
|
2013-04-19 16:33:25 -07:00
|
|
|
roBranches += fmt.Sprintf("%v=ro+wh:", layer)
|
2013-03-18 00:15:35 -07:00
|
|
|
}
|
|
|
|
branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
|
2013-04-15 12:05:46 +02:00
|
|
|
|
2013-06-05 12:59:05 +00:00
|
|
|
branches += ",xino=/dev/shm/aufs.xino"
|
|
|
|
|
2013-04-15 12:05:46 +02:00
|
|
|
//if error, try to load aufs kernel module
|
|
|
|
if err := mount("none", target, "aufs", 0, branches); err != nil {
|
|
|
|
log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...")
|
|
|
|
if err := exec.Command("modprobe", "aufs").Run(); err != nil {
|
|
|
|
return fmt.Errorf("Unable to load the AUFS module")
|
|
|
|
}
|
|
|
|
log.Printf("...module loaded.")
|
|
|
|
if err := mount("none", target, "aufs", 0, branches); err != nil {
|
|
|
|
return fmt.Errorf("Unable to mount using aufs")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2013-03-18 00:15:35 -07:00
|
|
|
}
|
|
|
|
|
2013-04-21 14:23:55 -07:00
|
|
|
// TarLayer returns a tar archive of the image's filesystem layer.
|
|
|
|
func (image *Image) TarLayer(compression Compression) (Archive, error) {
|
|
|
|
layerPath, err := image.layer()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return Tar(layerPath, compression)
|
|
|
|
}
|
|
|
|
|
2013-09-04 12:55:48 +02:00
|
|
|
func (image *Image) applyLayer(layer, target string) error {
|
|
|
|
oldmask := syscall.Umask(0)
|
|
|
|
defer syscall.Umask(oldmask)
|
|
|
|
err := filepath.Walk(layer, func(srcPath string, f os.FileInfo, err error) error {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip root
|
|
|
|
if srcPath == layer {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var srcStat syscall.Stat_t
|
|
|
|
err = syscall.Lstat(srcPath, &srcStat)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
relPath, err := filepath.Rel(layer, srcPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
targetPath := filepath.Join(target, relPath)
|
|
|
|
|
|
|
|
// Skip AUFS metadata
|
|
|
|
if matched, err := filepath.Match(".wh..wh.*", relPath); err != nil || matched {
|
|
|
|
if err != nil || !f.IsDir() {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return filepath.SkipDir
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find out what kind of modification happened
|
|
|
|
file := filepath.Base(srcPath)
|
|
|
|
|
|
|
|
// If there is a whiteout, then the file was removed
|
|
|
|
if strings.HasPrefix(file, ".wh.") {
|
|
|
|
originalFile := file[len(".wh."):]
|
|
|
|
deletePath := filepath.Join(filepath.Dir(targetPath), originalFile)
|
|
|
|
|
|
|
|
err = os.RemoveAll(deletePath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var targetStat = &syscall.Stat_t{}
|
|
|
|
err := syscall.Lstat(targetPath, targetStat)
|
|
|
|
if err != nil {
|
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
targetStat = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if targetStat != nil && !(targetStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR && srcStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR) {
|
|
|
|
// Unless both src and dest are directories we remove the target and recreate it
|
|
|
|
// This is a bit wasteful in the case of only a mode change, but that is unlikely
|
|
|
|
// to matter much
|
|
|
|
err = os.RemoveAll(targetPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
targetStat = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.IsDir() {
|
|
|
|
// Source is a directory
|
|
|
|
if targetStat == nil {
|
|
|
|
err = syscall.Mkdir(targetPath, srcStat.Mode&07777)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if srcStat.Mode&07777 != targetStat.Mode&07777 {
|
|
|
|
err = syscall.Chmod(targetPath, srcStat.Mode&07777)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if srcStat.Mode&syscall.S_IFLNK == syscall.S_IFLNK {
|
|
|
|
// Source is symlink
|
|
|
|
link, err := os.Readlink(srcPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = os.Symlink(link, targetPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if srcStat.Mode&syscall.S_IFBLK == syscall.S_IFBLK ||
|
|
|
|
srcStat.Mode&syscall.S_IFCHR == syscall.S_IFCHR ||
|
|
|
|
srcStat.Mode&syscall.S_IFIFO == syscall.S_IFIFO ||
|
|
|
|
srcStat.Mode&syscall.S_IFSOCK == syscall.S_IFSOCK {
|
|
|
|
// Source is special file
|
|
|
|
err = syscall.Mknod(targetPath, srcStat.Mode, int(srcStat.Rdev))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if srcStat.Mode&syscall.S_IFREG == syscall.S_IFREG {
|
|
|
|
// Source is regular file
|
|
|
|
fd, err := syscall.Open(targetPath, syscall.O_CREAT|syscall.O_WRONLY, srcStat.Mode&07777)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
dstFile := os.NewFile(uintptr(fd), targetPath)
|
|
|
|
srcFile, err := os.Open(srcPath)
|
|
|
|
_, err = io.Copy(dstFile, srcFile)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_ = srcFile.Close()
|
|
|
|
_ = dstFile.Close()
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("Unknown type for file %s", srcPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
if srcStat.Mode&syscall.S_IFLNK != syscall.S_IFLNK {
|
|
|
|
err = syscall.Chown(targetPath, int(srcStat.Uid), int(srcStat.Gid))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ts := []syscall.Timeval{
|
|
|
|
syscall.NsecToTimeval(srcStat.Atim.Nano()),
|
|
|
|
syscall.NsecToTimeval(srcStat.Mtim.Nano()),
|
|
|
|
}
|
|
|
|
syscall.Utimes(targetPath, ts)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (image *Image) ensureImageDevice(devices DeviceSet) error {
|
|
|
|
if devices.HasInitializedDevice(image.ID) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if image.Parent != "" && !devices.HasInitializedDevice(image.Parent) {
|
|
|
|
parentImg, err := image.GetParent()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error while getting parent image: %v", err)
|
|
|
|
}
|
|
|
|
err = parentImg.ensureImageDevice(devices)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
root, err := image.root()
|
|
|
|
if err != nil {
|
2013-03-18 00:15:35 -07:00
|
|
|
return err
|
|
|
|
}
|
2013-09-04 12:55:48 +02:00
|
|
|
|
|
|
|
mountDir := mountPath(root)
|
|
|
|
if err := os.Mkdir(mountDir, 0600); err != nil && !os.IsExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
mounted, err := Mounted(mountDir)
|
|
|
|
if err == nil && mounted {
|
|
|
|
log.Printf("Image %s is unexpectedly mounted, unmounting...", image.ID)
|
|
|
|
err = syscall.Unmount(mountDir, 0)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if devices.HasDevice(image.ID) {
|
|
|
|
log.Printf("Found non-initialized demove-mapper device for image %s, removing", image.ID)
|
|
|
|
err = devices.RemoveDevice(image.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("Creating device-mapper device for image id %s", image.ID)
|
|
|
|
|
|
|
|
err = devices.AddDevice(image.ID, image.Parent)
|
2013-03-18 00:15:35 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-09-04 12:55:48 +02:00
|
|
|
|
|
|
|
utils.Debugf("Mounting device %s at %s for image setup", image.ID, mountDir)
|
|
|
|
err = devices.MountDevice(image.ID, mountDir)
|
|
|
|
if err != nil {
|
|
|
|
_ = devices.RemoveDevice(image.ID)
|
2013-03-20 22:13:28 -07:00
|
|
|
return err
|
|
|
|
}
|
2013-09-04 12:55:48 +02:00
|
|
|
|
|
|
|
utils.Debugf("Applying layer %s at %s", image.ID, mountDir)
|
|
|
|
err = image.applyLayer(layerPath(root), mountDir)
|
|
|
|
if err != nil {
|
|
|
|
_ = devices.RemoveDevice(image.ID)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
utils.Debugf("Unmounting %s", mountDir)
|
|
|
|
err = syscall.Unmount(mountDir, 0)
|
|
|
|
if err != nil {
|
|
|
|
_ = devices.RemoveDevice(image.ID)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
devices.SetInitialized(image.ID)
|
|
|
|
|
|
|
|
// No need to the device-mapper device to hang around once we've written
|
|
|
|
// the image, it can be enabled on-demand when needed
|
|
|
|
devices.DeactivateDevice(image.ID)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error {
|
|
|
|
if mounted, err := Mounted(root); err != nil {
|
2013-03-20 22:13:28 -07:00
|
|
|
return err
|
2013-09-04 12:55:48 +02:00
|
|
|
} else if mounted {
|
|
|
|
return fmt.Errorf("%s is already mounted", root)
|
2013-03-20 22:13:28 -07:00
|
|
|
}
|
2013-09-04 12:55:48 +02:00
|
|
|
// Create the target directories if they don't exist
|
|
|
|
if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) {
|
2013-03-18 00:15:35 -07:00
|
|
|
return err
|
|
|
|
}
|
2013-09-04 12:55:48 +02:00
|
|
|
switch runtime.GetMountMethod() {
|
|
|
|
case MountMethodNone:
|
|
|
|
return fmt.Errorf("No supported Mount implementation")
|
|
|
|
|
|
|
|
case MountMethodAUFS:
|
|
|
|
if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
layers, err := image.layers()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := MountAUFS(layers, rw, root); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
case MountMethodDeviceMapper:
|
|
|
|
devices, err := runtime.GetDeviceSet()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = image.ensureImageDevice(devices)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !devices.HasDevice(id) {
|
|
|
|
utils.Debugf("Creating device %s for container based on image %s", id, image.ID)
|
|
|
|
err = devices.AddDevice(id, image.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
utils.Debugf("Mounting container %s at %s for container", id, root)
|
|
|
|
err = devices.MountDevice(id, root)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2013-03-18 00:15:35 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-03-20 22:12:38 -07:00
|
|
|
func (image *Image) Changes(rw string) ([]Change, error) {
|
|
|
|
layers, err := image.layers()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return Changes(layers, rw)
|
|
|
|
}
|
|
|
|
|
2013-06-04 18:00:22 +00:00
|
|
|
func (image *Image) ShortID() string {
|
|
|
|
return utils.TruncateID(image.ID)
|
2013-03-31 22:11:55 -07:00
|
|
|
}
|
|
|
|
|
2013-06-04 18:00:22 +00:00
|
|
|
func ValidateID(id string) error {
|
2013-03-18 00:15:35 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2013-06-04 18:00:22 +00:00
|
|
|
func GenerateID() string {
|
2013-03-26 16:46:27 -04:00
|
|
|
id := make([]byte, 32)
|
|
|
|
_, err := io.ReadFull(rand.Reader, id)
|
|
|
|
if err != nil {
|
|
|
|
panic(err) // This shouldn't happen
|
2013-03-21 01:07:07 -07:00
|
|
|
}
|
2013-03-26 16:46:27 -04:00
|
|
|
return hex.EncodeToString(id)
|
2013-03-18 00:15:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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(
|
2013-03-21 10:10:14 -07:00
|
|
|
func(img *Image) error {
|
2013-03-18 00:15:35 -07:00
|
|
|
parents = append(parents, img)
|
2013-03-21 10:10:14 -07:00
|
|
|
return nil
|
2013-03-18 00:15:35 -07:00
|
|
|
},
|
|
|
|
); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return parents, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// layers returns all the filesystem layers needed to mount an image
|
2013-03-21 10:10:14 -07:00
|
|
|
// FIXME: @shykes refactor this function with the new error handling
|
|
|
|
// (I'll do it if I have time tonight, I focus on the rest)
|
2013-03-18 00:15:35 -07:00
|
|
|
func (img *Image) layers() ([]string, error) {
|
|
|
|
var list []string
|
|
|
|
var e error
|
|
|
|
if err := img.WalkHistory(
|
2013-03-21 10:10:14 -07:00
|
|
|
func(img *Image) (err error) {
|
2013-03-18 00:15:35 -07:00
|
|
|
if layer, err := img.layer(); err != nil {
|
|
|
|
e = err
|
|
|
|
} else if layer != "" {
|
|
|
|
list = append(list, layer)
|
|
|
|
}
|
2013-03-21 10:10:14 -07:00
|
|
|
return err
|
2013-03-18 00:15:35 -07:00
|
|
|
},
|
|
|
|
); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else if e != nil { // Did an error occur inside the handler?
|
|
|
|
return nil, e
|
|
|
|
}
|
|
|
|
if len(list) == 0 {
|
2013-06-04 18:00:22 +00:00
|
|
|
return nil, fmt.Errorf("No layer found for image %s\n", img.ID)
|
2013-03-18 00:15:35 -07:00
|
|
|
}
|
2013-06-14 16:56:08 -07:00
|
|
|
|
|
|
|
// Inject the dockerinit layer (empty place-holder for mount-binding dockerinit)
|
|
|
|
if dockerinitLayer, err := img.getDockerInitLayer(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
list = append([]string{dockerinitLayer}, list...)
|
|
|
|
}
|
2013-03-18 00:15:35 -07:00
|
|
|
return list, nil
|
|
|
|
}
|
|
|
|
|
2013-03-21 10:10:14 -07:00
|
|
|
func (img *Image) WalkHistory(handler func(*Image) error) (err error) {
|
2013-03-18 00:15:35 -07:00
|
|
|
currentImg := img
|
|
|
|
for currentImg != nil {
|
|
|
|
if handler != nil {
|
2013-03-21 10:10:14 -07:00
|
|
|
if err := handler(currentImg); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-03-18 00:15:35 -07:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2013-06-14 16:56:08 -07:00
|
|
|
func (img *Image) getDockerInitLayer() (string, error) {
|
|
|
|
if img.graph == nil {
|
|
|
|
return "", fmt.Errorf("Can't lookup dockerinit layer of unregistered image")
|
|
|
|
}
|
|
|
|
return img.graph.getDockerInitLayer()
|
|
|
|
}
|
|
|
|
|
2013-03-18 00:15:35 -07:00
|
|
|
func (img *Image) root() (string, error) {
|
|
|
|
if img.graph == nil {
|
|
|
|
return "", fmt.Errorf("Can't lookup root of unregistered image")
|
|
|
|
}
|
2013-06-04 18:00:22 +00:00
|
|
|
return img.graph.imageRoot(img.ID), nil
|
2013-03-18 00:15:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2013-04-26 14:00:09 -07:00
|
|
|
|
2013-06-14 10:05:01 +00:00
|
|
|
func (img *Image) getParentsSize(size int64) int64 {
|
2013-05-13 15:10:26 +02:00
|
|
|
parentImage, err := img.GetParent()
|
|
|
|
if err != nil || parentImage == nil {
|
|
|
|
return size
|
|
|
|
}
|
|
|
|
size += parentImage.Size
|
2013-06-14 10:05:01 +00:00
|
|
|
return parentImage.getParentsSize(size)
|
2013-05-13 15:10:26 +02:00
|
|
|
}
|
2013-05-22 13:41:29 +00:00
|
|
|
|
2013-05-15 01:41:39 +00:00
|
|
|
// Build an Image object from raw json data
|
2013-06-04 18:00:22 +00:00
|
|
|
func NewImgJSON(src []byte) (*Image, error) {
|
2013-05-15 01:41:39 +00:00
|
|
|
ret := &Image{}
|
|
|
|
|
|
|
|
utils.Debugf("Json string: {%s}\n", src)
|
|
|
|
// FIXME: Is there a cleaner way to "purify" the input json?
|
|
|
|
if err := json.Unmarshal(src, ret); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return ret, nil
|
|
|
|
}
|