mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
01ba0a935b
The image store abstracts image handling. It keeps track of the available images, and makes it possible to delete existing images or register new ones. The image store holds references to the underlying layers for each image. The image/v1 package provides compatibility functions for interoperating with older (non-content-addressable) image structures. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
284 lines
6.5 KiB
Go
284 lines
6.5 KiB
Go
package tarexport
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/distribution/reference"
|
|
"github.com/docker/docker/image"
|
|
"github.com/docker/docker/image/v1"
|
|
"github.com/docker/docker/layer"
|
|
"github.com/docker/docker/pkg/archive"
|
|
"github.com/docker/docker/pkg/chrootarchive"
|
|
"github.com/docker/docker/pkg/symlink"
|
|
)
|
|
|
|
func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer) error {
|
|
tmpDir, err := ioutil.TempDir("", "docker-import-")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
if err := chrootarchive.Untar(inTar, tmpDir, nil); err != nil {
|
|
return err
|
|
}
|
|
// read manifest, if no file then load in legacy mode
|
|
manifestPath, err := safePath(tmpDir, manifestFileName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
manifestFile, err := os.Open(manifestPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return l.legacyLoad(tmpDir, outStream)
|
|
}
|
|
return manifestFile.Close()
|
|
}
|
|
defer manifestFile.Close()
|
|
|
|
var manifest []manifestItem
|
|
if err := json.NewDecoder(manifestFile).Decode(&manifest); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, m := range manifest {
|
|
configPath, err := safePath(tmpDir, m.Config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
config, err := ioutil.ReadFile(configPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
img, err := image.NewFromJSON(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var rootFS image.RootFS
|
|
rootFS = *img.RootFS
|
|
rootFS.DiffIDs = nil
|
|
|
|
if expected, actual := len(m.Layers), len(img.RootFS.DiffIDs); expected != actual {
|
|
return fmt.Errorf("invalid manifest, layers length mismatch: expected %q, got %q", expected, actual)
|
|
}
|
|
|
|
for i, diffID := range img.RootFS.DiffIDs {
|
|
layerPath, err := safePath(tmpDir, m.Layers[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newLayer, err := l.loadLayer(layerPath, rootFS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer layer.ReleaseAndLog(l.ls, newLayer)
|
|
if expected, actual := diffID, newLayer.DiffID(); expected != actual {
|
|
return fmt.Errorf("invalid diffID for layer %d: expected %q, got %q", i, expected, actual)
|
|
}
|
|
rootFS.Append(diffID)
|
|
}
|
|
|
|
imgID, err := l.is.Create(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, repoTag := range m.RepoTags {
|
|
named, err := reference.ParseNamed(repoTag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ref, ok := named.(reference.NamedTagged)
|
|
if !ok {
|
|
return fmt.Errorf("invalid tag %q", repoTag)
|
|
}
|
|
l.setLoadedTag(ref, imgID, outStream)
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS) (layer.Layer, error) {
|
|
rawTar, err := os.Open(filename)
|
|
if err != nil {
|
|
logrus.Debugf("Error reading embedded tar: %v", err)
|
|
return nil, err
|
|
}
|
|
inflatedLayerData, err := archive.DecompressStream(rawTar)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer rawTar.Close()
|
|
defer inflatedLayerData.Close()
|
|
|
|
return l.ls.Register(inflatedLayerData, rootFS.ChainID())
|
|
}
|
|
|
|
func (l *tarexporter) setLoadedTag(ref reference.NamedTagged, imgID image.ID, outStream io.Writer) error {
|
|
if prevID, err := l.ts.Get(ref); err == nil && prevID != imgID {
|
|
fmt.Fprintf(outStream, "The image %s already exists, renaming the old one with ID %s to empty string\n", ref.String(), string(prevID)) // todo: this message is wrong in case of multiple tags
|
|
}
|
|
|
|
if err := l.ts.Add(ref, imgID, true); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *tarexporter) legacyLoad(tmpDir string, outStream io.Writer) error {
|
|
legacyLoadedMap := make(map[string]image.ID)
|
|
|
|
dirs, err := ioutil.ReadDir(tmpDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// every dir represents an image
|
|
for _, d := range dirs {
|
|
if d.IsDir() {
|
|
if err := l.legacyLoadImage(d.Name(), tmpDir, legacyLoadedMap); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// load tags from repositories file
|
|
repositoriesPath, err := safePath(tmpDir, legacyRepositoriesFileName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
repositoriesFile, err := os.Open(repositoriesPath)
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
return repositoriesFile.Close()
|
|
}
|
|
defer repositoriesFile.Close()
|
|
|
|
repositories := make(map[string]map[string]string)
|
|
if err := json.NewDecoder(repositoriesFile).Decode(&repositories); err != nil {
|
|
return err
|
|
}
|
|
|
|
for name, tagMap := range repositories {
|
|
for tag, oldID := range tagMap {
|
|
imgID, ok := legacyLoadedMap[oldID]
|
|
if !ok {
|
|
return fmt.Errorf("invalid target ID: %v", oldID)
|
|
}
|
|
named, err := reference.WithName(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ref, err := reference.WithTag(named, tag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
l.setLoadedTag(ref, imgID, outStream)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l *tarexporter) legacyLoadImage(oldID, sourceDir string, loadedMap map[string]image.ID) error {
|
|
if _, loaded := loadedMap[oldID]; loaded {
|
|
return nil
|
|
}
|
|
configPath, err := safePath(sourceDir, filepath.Join(oldID, legacyConfigFileName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
imageJSON, err := ioutil.ReadFile(configPath)
|
|
if err != nil {
|
|
logrus.Debugf("Error reading json: %v", err)
|
|
return err
|
|
}
|
|
|
|
var img struct{ Parent string }
|
|
if err := json.Unmarshal(imageJSON, &img); err != nil {
|
|
return err
|
|
}
|
|
|
|
var parentID image.ID
|
|
if img.Parent != "" {
|
|
for {
|
|
var loaded bool
|
|
if parentID, loaded = loadedMap[img.Parent]; !loaded {
|
|
if err := l.legacyLoadImage(img.Parent, sourceDir, loadedMap); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// todo: try to connect with migrate code
|
|
rootFS := image.NewRootFS()
|
|
var history []image.History
|
|
|
|
if parentID != "" {
|
|
parentImg, err := l.is.Get(parentID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rootFS = parentImg.RootFS
|
|
history = parentImg.History
|
|
}
|
|
|
|
layerPath, err := safePath(sourceDir, filepath.Join(oldID, legacyLayerFileName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newLayer, err := l.loadLayer(layerPath, *rootFS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rootFS.Append(newLayer.DiffID())
|
|
|
|
h, err := v1.HistoryFromConfig(imageJSON, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
history = append(history, h)
|
|
|
|
config, err := v1.MakeConfigFromV1Config(imageJSON, rootFS, history)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
imgID, err := l.is.Create(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
metadata, err := l.ls.Release(newLayer)
|
|
layer.LogReleaseMetadata(metadata)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if parentID != "" {
|
|
if err := l.is.SetParent(imgID, parentID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
loadedMap[oldID] = imgID
|
|
return nil
|
|
}
|
|
|
|
func safePath(base, path string) (string, error) {
|
|
return symlink.FollowSymlinkInScope(filepath.Join(base, path), base)
|
|
}
|