mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
504e67b867
Generate a hash chain involving the image configuration, layer digests, and parent image hashes. Use the digests to compute IDs for each image in a manifest, instead of using the remotely specified IDs. To avoid breaking users' caches, check for images already in the graph under old IDs, and avoid repulling an image if the version on disk under the legacy ID ends up with the same digest that was computed from the manifest for that image. When a calculated ID already exists in the graph but can't be verified, continue trying SHA256(digest) until a suitable ID is found. "save" and "load" are not changed to use a similar scheme. "load" will preserve the IDs present in the tar file. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
174 lines
4.3 KiB
Go
174 lines
4.3 KiB
Go
package graph
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/docker/pkg/archive"
|
|
"github.com/docker/docker/pkg/parsers"
|
|
"github.com/docker/docker/registry"
|
|
)
|
|
|
|
// ImageExport exports list of images to a output stream specified in the
|
|
// config. The exported images are archived into a tar when written to the
|
|
// output stream. All images with the given tag and all versions containing the
|
|
// same tag are exported. names is the set of tags to export, and outStream
|
|
// is the writer which the images are written to.
|
|
func (s *TagStore) ImageExport(names []string, outStream io.Writer) error {
|
|
// get image json
|
|
tempdir, err := ioutil.TempDir("", "docker-export-")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer os.RemoveAll(tempdir)
|
|
|
|
rootRepoMap := map[string]Repository{}
|
|
addKey := func(name string, tag string, id string) {
|
|
logrus.Debugf("add key [%s:%s]", name, tag)
|
|
if repo, ok := rootRepoMap[name]; !ok {
|
|
rootRepoMap[name] = Repository{tag: id}
|
|
} else {
|
|
repo[tag] = id
|
|
}
|
|
}
|
|
for _, name := range names {
|
|
name = registry.NormalizeLocalName(name)
|
|
logrus.Debugf("Serializing %s", name)
|
|
rootRepo := s.Repositories[name]
|
|
if rootRepo != nil {
|
|
// this is a base repo name, like 'busybox'
|
|
for tag, id := range rootRepo {
|
|
addKey(name, tag, id)
|
|
if err := s.exportImage(id, tempdir); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
img, err := s.LookupImage(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if img != nil {
|
|
// This is a named image like 'busybox:latest'
|
|
repoName, repoTag := parsers.ParseRepositoryTag(name)
|
|
|
|
// check this length, because a lookup of a truncated has will not have a tag
|
|
// and will not need to be added to this map
|
|
if len(repoTag) > 0 {
|
|
addKey(repoName, repoTag, img.ID)
|
|
}
|
|
if err := s.exportImage(img.ID, tempdir); err != nil {
|
|
return err
|
|
}
|
|
|
|
} else {
|
|
// this must be an ID that didn't get looked up just right?
|
|
if err := s.exportImage(name, tempdir); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
logrus.Debugf("End Serializing %s", name)
|
|
}
|
|
// write repositories, if there is something to write
|
|
if len(rootRepoMap) > 0 {
|
|
f, err := os.OpenFile(filepath.Join(tempdir, "repositories"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
f.Close()
|
|
return err
|
|
}
|
|
if err := json.NewEncoder(f).Encode(rootRepoMap); err != nil {
|
|
return err
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
return err
|
|
}
|
|
if err := os.Chtimes(filepath.Join(tempdir, "repositories"), time.Unix(0, 0), time.Unix(0, 0)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
logrus.Debugf("There were no repositories to write")
|
|
}
|
|
|
|
fs, err := archive.Tar(tempdir, archive.Uncompressed)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer fs.Close()
|
|
|
|
if _, err := io.Copy(outStream, fs); err != nil {
|
|
return err
|
|
}
|
|
logrus.Debugf("End export image")
|
|
return nil
|
|
}
|
|
|
|
func (s *TagStore) exportImage(name, tempdir string) error {
|
|
for n := name; n != ""; {
|
|
img, err := s.LookupImage(n)
|
|
if err != nil || img == nil {
|
|
return fmt.Errorf("No such image %s", n)
|
|
}
|
|
|
|
// temporary directory
|
|
tmpImageDir := filepath.Join(tempdir, n)
|
|
if err := os.Mkdir(tmpImageDir, os.FileMode(0755)); err != nil {
|
|
if os.IsExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
var version = "1.0"
|
|
var versionBuf = []byte(version)
|
|
|
|
if err := ioutil.WriteFile(filepath.Join(tmpImageDir, "VERSION"), versionBuf, os.FileMode(0644)); err != nil {
|
|
return err
|
|
}
|
|
|
|
imageInspectRaw, err := json.Marshal(img)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// serialize json
|
|
json, err := os.Create(filepath.Join(tmpImageDir, "json"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
written, err := json.Write(imageInspectRaw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if written != len(imageInspectRaw) {
|
|
logrus.Warnf("%d byes should have been written instead %d have been written", written, len(imageInspectRaw))
|
|
}
|
|
|
|
// serialize filesystem
|
|
fsTar, err := os.Create(filepath.Join(tmpImageDir, "layer.tar"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := s.ImageTarLayer(n, fsTar); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, fname := range []string{"", "VERSION", "json", "layer.tar"} {
|
|
if err := os.Chtimes(filepath.Join(tmpImageDir, fname), img.Created, img.Created); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// try again with parent
|
|
n = img.Parent
|
|
}
|
|
return nil
|
|
}
|