github.com/resin-io/docker@v1.13.1/image/tarexport/load.go (about)

     1  package tarexport
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  
    12  	"github.com/Sirupsen/logrus"
    13  	"github.com/docker/distribution"
    14  	"github.com/docker/distribution/digest"
    15  	"github.com/docker/docker/image"
    16  	"github.com/docker/docker/image/v1"
    17  	"github.com/docker/docker/layer"
    18  	"github.com/docker/docker/pkg/archive"
    19  	"github.com/docker/docker/pkg/chrootarchive"
    20  	"github.com/docker/docker/pkg/progress"
    21  	"github.com/docker/docker/pkg/streamformatter"
    22  	"github.com/docker/docker/pkg/stringid"
    23  	"github.com/docker/docker/pkg/symlink"
    24  	"github.com/docker/docker/pkg/system"
    25  	"github.com/docker/docker/reference"
    26  )
    27  
    28  func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool) error {
    29  	var (
    30  		sf             = streamformatter.NewJSONStreamFormatter()
    31  		progressOutput progress.Output
    32  	)
    33  	if !quiet {
    34  		progressOutput = sf.NewProgressOutput(outStream, false)
    35  	}
    36  	outStream = &streamformatter.StdoutFormatter{Writer: outStream, StreamFormatter: streamformatter.NewJSONStreamFormatter()}
    37  
    38  	tmpDir, err := ioutil.TempDir("", "docker-import-")
    39  	if err != nil {
    40  		return err
    41  	}
    42  	defer os.RemoveAll(tmpDir)
    43  
    44  	if err := chrootarchive.Untar(inTar, tmpDir, nil); err != nil {
    45  		return err
    46  	}
    47  	// read manifest, if no file then load in legacy mode
    48  	manifestPath, err := safePath(tmpDir, manifestFileName)
    49  	if err != nil {
    50  		return err
    51  	}
    52  	manifestFile, err := os.Open(manifestPath)
    53  	if err != nil {
    54  		if os.IsNotExist(err) {
    55  			return l.legacyLoad(tmpDir, outStream, progressOutput)
    56  		}
    57  		return err
    58  	}
    59  	defer manifestFile.Close()
    60  
    61  	var manifest []manifestItem
    62  	if err := json.NewDecoder(manifestFile).Decode(&manifest); err != nil {
    63  		return err
    64  	}
    65  
    66  	var parentLinks []parentLink
    67  	var imageIDsStr string
    68  	var imageRefCount int
    69  
    70  	for _, m := range manifest {
    71  		configPath, err := safePath(tmpDir, m.Config)
    72  		if err != nil {
    73  			return err
    74  		}
    75  		config, err := ioutil.ReadFile(configPath)
    76  		if err != nil {
    77  			return err
    78  		}
    79  		img, err := image.NewFromJSON(config)
    80  		if err != nil {
    81  			return err
    82  		}
    83  		var rootFS image.RootFS
    84  		rootFS = *img.RootFS
    85  		rootFS.DiffIDs = nil
    86  
    87  		if expected, actual := len(m.Layers), len(img.RootFS.DiffIDs); expected != actual {
    88  			return fmt.Errorf("invalid manifest, layers length mismatch: expected %q, got %q", expected, actual)
    89  		}
    90  
    91  		for i, diffID := range img.RootFS.DiffIDs {
    92  			layerPath, err := safePath(tmpDir, m.Layers[i])
    93  			if err != nil {
    94  				return err
    95  			}
    96  			r := rootFS
    97  			r.Append(diffID)
    98  			newLayer, err := l.ls.Get(r.ChainID())
    99  			if err != nil {
   100  				newLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), m.LayerSources[diffID], progressOutput)
   101  				if err != nil {
   102  					return err
   103  				}
   104  			}
   105  			defer layer.ReleaseAndLog(l.ls, newLayer)
   106  			if expected, actual := diffID, newLayer.DiffID(); expected != actual {
   107  				return fmt.Errorf("invalid diffID for layer %d: expected %q, got %q", i, expected, actual)
   108  			}
   109  			rootFS.Append(diffID)
   110  		}
   111  
   112  		imgID, err := l.is.Create(config)
   113  		if err != nil {
   114  			return err
   115  		}
   116  		imageIDsStr += fmt.Sprintf("Loaded image ID: %s\n", imgID)
   117  
   118  		imageRefCount = 0
   119  		for _, repoTag := range m.RepoTags {
   120  			named, err := reference.ParseNamed(repoTag)
   121  			if err != nil {
   122  				return err
   123  			}
   124  			ref, ok := named.(reference.NamedTagged)
   125  			if !ok {
   126  				return fmt.Errorf("invalid tag %q", repoTag)
   127  			}
   128  			l.setLoadedTag(ref, imgID.Digest(), outStream)
   129  			outStream.Write([]byte(fmt.Sprintf("Loaded image: %s\n", ref)))
   130  			imageRefCount++
   131  		}
   132  
   133  		parentLinks = append(parentLinks, parentLink{imgID, m.Parent})
   134  		l.loggerImgEvent.LogImageEvent(imgID.String(), imgID.String(), "load")
   135  	}
   136  
   137  	for _, p := range validatedParentLinks(parentLinks) {
   138  		if p.parentID != "" {
   139  			if err := l.setParentID(p.id, p.parentID); err != nil {
   140  				return err
   141  			}
   142  		}
   143  	}
   144  
   145  	if imageRefCount == 0 {
   146  		outStream.Write([]byte(imageIDsStr))
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  func (l *tarexporter) setParentID(id, parentID image.ID) error {
   153  	img, err := l.is.Get(id)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	parent, err := l.is.Get(parentID)
   158  	if err != nil {
   159  		return err
   160  	}
   161  	if !checkValidParent(img, parent) {
   162  		return fmt.Errorf("image %v is not a valid parent for %v", parent.ID(), img.ID())
   163  	}
   164  	return l.is.SetParent(id, parentID)
   165  }
   166  
   167  func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, foreignSrc distribution.Descriptor, progressOutput progress.Output) (layer.Layer, error) {
   168  	// We use system.OpenSequential to use sequential file access on Windows, avoiding
   169  	// depleting the standby list. On Linux, this equates to a regular os.Open.
   170  	rawTar, err := system.OpenSequential(filename)
   171  	if err != nil {
   172  		logrus.Debugf("Error reading embedded tar: %v", err)
   173  		return nil, err
   174  	}
   175  	defer rawTar.Close()
   176  
   177  	var r io.Reader
   178  	if progressOutput != nil {
   179  		fileInfo, err := rawTar.Stat()
   180  		if err != nil {
   181  			logrus.Debugf("Error statting file: %v", err)
   182  			return nil, err
   183  		}
   184  
   185  		r = progress.NewProgressReader(rawTar, progressOutput, fileInfo.Size(), stringid.TruncateID(id), "Loading layer")
   186  	} else {
   187  		r = rawTar
   188  	}
   189  
   190  	inflatedLayerData, err := archive.DecompressStream(r)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	defer inflatedLayerData.Close()
   195  
   196  	if ds, ok := l.ls.(layer.DescribableStore); ok {
   197  		return ds.RegisterWithDescriptor(inflatedLayerData, rootFS.ChainID(), foreignSrc)
   198  	}
   199  	return l.ls.Register(inflatedLayerData, rootFS.ChainID())
   200  }
   201  
   202  func (l *tarexporter) setLoadedTag(ref reference.NamedTagged, imgID digest.Digest, outStream io.Writer) error {
   203  	if prevID, err := l.rs.Get(ref); err == nil && prevID != imgID {
   204  		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
   205  	}
   206  
   207  	if err := l.rs.AddTag(ref, imgID, true); err != nil {
   208  		return err
   209  	}
   210  	return nil
   211  }
   212  
   213  func (l *tarexporter) legacyLoad(tmpDir string, outStream io.Writer, progressOutput progress.Output) error {
   214  	legacyLoadedMap := make(map[string]image.ID)
   215  
   216  	dirs, err := ioutil.ReadDir(tmpDir)
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	// every dir represents an image
   222  	for _, d := range dirs {
   223  		if d.IsDir() {
   224  			if err := l.legacyLoadImage(d.Name(), tmpDir, legacyLoadedMap, progressOutput); err != nil {
   225  				return err
   226  			}
   227  		}
   228  	}
   229  
   230  	// load tags from repositories file
   231  	repositoriesPath, err := safePath(tmpDir, legacyRepositoriesFileName)
   232  	if err != nil {
   233  		return err
   234  	}
   235  	repositoriesFile, err := os.Open(repositoriesPath)
   236  	if err != nil {
   237  		return err
   238  	}
   239  	defer repositoriesFile.Close()
   240  
   241  	repositories := make(map[string]map[string]string)
   242  	if err := json.NewDecoder(repositoriesFile).Decode(&repositories); err != nil {
   243  		return err
   244  	}
   245  
   246  	for name, tagMap := range repositories {
   247  		for tag, oldID := range tagMap {
   248  			imgID, ok := legacyLoadedMap[oldID]
   249  			if !ok {
   250  				return fmt.Errorf("invalid target ID: %v", oldID)
   251  			}
   252  			named, err := reference.WithName(name)
   253  			if err != nil {
   254  				return err
   255  			}
   256  			ref, err := reference.WithTag(named, tag)
   257  			if err != nil {
   258  				return err
   259  			}
   260  			l.setLoadedTag(ref, imgID.Digest(), outStream)
   261  		}
   262  	}
   263  
   264  	return nil
   265  }
   266  
   267  func (l *tarexporter) legacyLoadImage(oldID, sourceDir string, loadedMap map[string]image.ID, progressOutput progress.Output) error {
   268  	if _, loaded := loadedMap[oldID]; loaded {
   269  		return nil
   270  	}
   271  	configPath, err := safePath(sourceDir, filepath.Join(oldID, legacyConfigFileName))
   272  	if err != nil {
   273  		return err
   274  	}
   275  	imageJSON, err := ioutil.ReadFile(configPath)
   276  	if err != nil {
   277  		logrus.Debugf("Error reading json: %v", err)
   278  		return err
   279  	}
   280  
   281  	var img struct{ Parent string }
   282  	if err := json.Unmarshal(imageJSON, &img); err != nil {
   283  		return err
   284  	}
   285  
   286  	var parentID image.ID
   287  	if img.Parent != "" {
   288  		for {
   289  			var loaded bool
   290  			if parentID, loaded = loadedMap[img.Parent]; !loaded {
   291  				if err := l.legacyLoadImage(img.Parent, sourceDir, loadedMap, progressOutput); err != nil {
   292  					return err
   293  				}
   294  			} else {
   295  				break
   296  			}
   297  		}
   298  	}
   299  
   300  	// todo: try to connect with migrate code
   301  	rootFS := image.NewRootFS()
   302  	var history []image.History
   303  
   304  	if parentID != "" {
   305  		parentImg, err := l.is.Get(parentID)
   306  		if err != nil {
   307  			return err
   308  		}
   309  
   310  		rootFS = parentImg.RootFS
   311  		history = parentImg.History
   312  	}
   313  
   314  	layerPath, err := safePath(sourceDir, filepath.Join(oldID, legacyLayerFileName))
   315  	if err != nil {
   316  		return err
   317  	}
   318  	newLayer, err := l.loadLayer(layerPath, *rootFS, oldID, distribution.Descriptor{}, progressOutput)
   319  	if err != nil {
   320  		return err
   321  	}
   322  	rootFS.Append(newLayer.DiffID())
   323  
   324  	h, err := v1.HistoryFromConfig(imageJSON, false)
   325  	if err != nil {
   326  		return err
   327  	}
   328  	history = append(history, h)
   329  
   330  	config, err := v1.MakeConfigFromV1Config(imageJSON, rootFS, history)
   331  	if err != nil {
   332  		return err
   333  	}
   334  	imgID, err := l.is.Create(config)
   335  	if err != nil {
   336  		return err
   337  	}
   338  
   339  	metadata, err := l.ls.Release(newLayer)
   340  	layer.LogReleaseMetadata(metadata)
   341  	if err != nil {
   342  		return err
   343  	}
   344  
   345  	if parentID != "" {
   346  		if err := l.is.SetParent(imgID, parentID); err != nil {
   347  			return err
   348  		}
   349  	}
   350  
   351  	loadedMap[oldID] = imgID
   352  	return nil
   353  }
   354  
   355  func safePath(base, path string) (string, error) {
   356  	return symlink.FollowSymlinkInScope(filepath.Join(base, path), base)
   357  }
   358  
   359  type parentLink struct {
   360  	id, parentID image.ID
   361  }
   362  
   363  func validatedParentLinks(pl []parentLink) (ret []parentLink) {
   364  mainloop:
   365  	for i, p := range pl {
   366  		ret = append(ret, p)
   367  		for _, p2 := range pl {
   368  			if p2.id == p.parentID && p2.id != p.id {
   369  				continue mainloop
   370  			}
   371  		}
   372  		ret[i].parentID = ""
   373  	}
   374  	return
   375  }
   376  
   377  func checkValidParent(img, parent *image.Image) bool {
   378  	if len(img.History) == 0 && len(parent.History) == 0 {
   379  		return true // having history is not mandatory
   380  	}
   381  	if len(img.History)-len(parent.History) != 1 {
   382  		return false
   383  	}
   384  	for i, h := range parent.History {
   385  		if !reflect.DeepEqual(h, img.History[i]) {
   386  			return false
   387  		}
   388  	}
   389  	return true
   390  }