| package graph |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "time" |
| |
| "github.com/Sirupsen/logrus" |
| "github.com/docker/distribution/digest" |
| "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) |
| |
| // Skip digests on save |
| if _, err := digest.ParseDigest(repoTag); err == nil { |
| repoTag = "" |
| } |
| |
| // 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 |
| } |