summaryrefslogtreecommitdiff
path: root/image/image.go
diff options
context:
space:
mode:
Diffstat (limited to 'image/image.go')
-rw-r--r--image/image.go292
1 files changed, 292 insertions, 0 deletions
diff --git a/image/image.go b/image/image.go
new file mode 100644
index 0000000000..33503bad5a
--- /dev/null
+++ b/image/image.go
@@ -0,0 +1,292 @@
+package image
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/dotcloud/docker/archive"
+ "github.com/dotcloud/docker/runconfig"
+ "github.com/dotcloud/docker/runtime/graphdriver"
+ "github.com/dotcloud/docker/utils"
+ "io/ioutil"
+ "os"
+ "path"
+ "strconv"
+ "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 runconfig.Config `json:"container_config,omitempty"`
+ DockerVersion string `json:"docker_version,omitempty"`
+ Author string `json:"author,omitempty"`
+ Config *runconfig.Config `json:"config,omitempty"`
+ Architecture string `json:"architecture,omitempty"`
+ OS string `json:"os,omitempty"`
+ Size int64
+
+ graph Graph
+}
+
+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 := utils.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.ArchiveReader, 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
+}
+
+func (img *Image) SetGraph(graph Graph) {
+ img.graph = graph
+}
+
+// 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 utils.NewReadCloserWrapper(archive, func() error {
+ err := archive.Close()
+ driver.Put(img.ID)
+ return err
+ }), 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 utils.NewReadCloserWrapper(archive, func() error {
+ err := archive.Close()
+ driver.Put(img.ID)
+ return err
+ }), 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
+}
+
+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
+}