d277a4c0f8
Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com> (github: crosbymichael)
309 lines
7.2 KiB
Go
309 lines
7.2 KiB
Go
package docker
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/dotcloud/docker/archive"
|
|
"github.com/dotcloud/docker/graphdriver"
|
|
"github.com/dotcloud/docker/utils"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"strconv"
|
|
"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"`
|
|
DockerVersion string `json:"docker_version,omitempty"`
|
|
Author string `json:"author,omitempty"`
|
|
Config *Config `json:"config,omitempty"`
|
|
Architecture string `json:"architecture,omitempty"`
|
|
OS string `json:"os,omitempty"`
|
|
graph *Graph
|
|
Size int64
|
|
}
|
|
|
|
func LoadImage(root string) (*Image, error) {
|
|
// Load the json data
|
|
jsonData, err := ioutil.ReadFile(jsonPath(root))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
img := &Image{}
|
|
|
|
if err := json.Unmarshal(jsonData, img); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := ValidateID(img.ID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if buf, err := ioutil.ReadFile(path.Join(root, "layersize")); err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
// If the layersize file does not exist then set the size to a negative number
|
|
// because a layer size of 0 (zero) is valid
|
|
img.Size = -1
|
|
} else {
|
|
size, err := strconv.Atoi(string(buf))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
img.Size = int64(size)
|
|
}
|
|
|
|
return img, nil
|
|
}
|
|
|
|
func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, layer string) error {
|
|
// Store the layer
|
|
var (
|
|
size int64
|
|
err error
|
|
driver = img.graph.driver
|
|
)
|
|
if err := os.MkdirAll(layer, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
// If layerData is not nil, unpack it into the new layer
|
|
if layerData != nil {
|
|
if differ, ok := driver.(graphdriver.Differ); ok {
|
|
if err := differ.ApplyDiff(img.ID, layerData); err != nil {
|
|
return err
|
|
}
|
|
|
|
if size, err = differ.DiffSize(img.ID); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
start := time.Now().UTC()
|
|
utils.Debugf("Start untar layer")
|
|
if err := archive.ApplyLayer(layer, layerData); err != nil {
|
|
return err
|
|
}
|
|
utils.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
|
|
|
|
if img.Parent == "" {
|
|
if size, err = utils.TreeSize(layer); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
parent, err := driver.Get(img.Parent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer driver.Put(img.Parent)
|
|
changes, err := archive.ChangesDirs(layer, parent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
size = archive.ChangesSize(layer, changes)
|
|
}
|
|
}
|
|
}
|
|
|
|
img.Size = size
|
|
if err := img.SaveSize(root); err != nil {
|
|
return err
|
|
}
|
|
|
|
// If raw json is provided, then use it
|
|
if jsonData != nil {
|
|
if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if jsonData, err = json.Marshal(img); err != nil {
|
|
return err
|
|
}
|
|
if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SaveSize stores the current `size` value of `img` in the directory `root`.
|
|
func (img *Image) SaveSize(root string) error {
|
|
if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(img.Size))), 0600); err != nil {
|
|
return fmt.Errorf("Error storing image size in %s/layersize: %s", root, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func jsonPath(root string) string {
|
|
return path.Join(root, "json")
|
|
}
|
|
|
|
// TarLayer returns a tar archive of the image's filesystem layer.
|
|
func (img *Image) TarLayer() (arch archive.Archive, err error) {
|
|
if img.graph == nil {
|
|
return nil, fmt.Errorf("Can't load storage driver for unregistered image %s", img.ID)
|
|
}
|
|
driver := img.graph.driver
|
|
if differ, ok := driver.(graphdriver.Differ); ok {
|
|
return differ.Diff(img.ID)
|
|
}
|
|
|
|
imgFs, err := driver.Get(img.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
driver.Put(img.ID)
|
|
}
|
|
}()
|
|
|
|
if img.Parent == "" {
|
|
archive, err := archive.Tar(imgFs, archive.Uncompressed)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return EofReader(archive, func() { driver.Put(img.ID) }), nil
|
|
}
|
|
|
|
parentFs, err := driver.Get(img.Parent)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer driver.Put(img.Parent)
|
|
changes, err := archive.ChangesDirs(imgFs, parentFs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
archive, err := archive.ExportChanges(imgFs, changes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return EofReader(archive, func() { driver.Put(img.ID) }), 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 {
|
|
for {
|
|
id := make([]byte, 32)
|
|
if _, err := io.ReadFull(rand.Reader, id); err != nil {
|
|
panic(err) // This shouldn't happen
|
|
}
|
|
value := hex.EncodeToString(id)
|
|
// if we try to parse the truncated for as an int and we don't have
|
|
// an error then the value is all numberic and causes issues when
|
|
// used as a hostname. ref #3869
|
|
if _, err := strconv.Atoi(utils.TruncateID(value)); err == nil {
|
|
continue
|
|
}
|
|
return value
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (img *Image) getParentsSize(size int64) int64 {
|
|
parentImage, err := img.GetParent()
|
|
if err != nil || parentImage == nil {
|
|
return size
|
|
}
|
|
size += parentImage.Size
|
|
return parentImage.getParentsSize(size)
|
|
}
|
|
|
|
// Depth returns the number of parents for a
|
|
// current image
|
|
func (img *Image) Depth() (int, error) {
|
|
var (
|
|
count = 0
|
|
parent = img
|
|
err error
|
|
)
|
|
|
|
for parent != nil {
|
|
count++
|
|
parent, err = parent.GetParent()
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
// Build an Image object from raw json data
|
|
func NewImgJSON(src []byte) (*Image, error) {
|
|
ret := &Image{}
|
|
|
|
utils.Debugf("Json string: {%s}", 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
|
|
}
|