github.com/OpenFlowLabs/moby@v17.12.1-ce-rc2+incompatible/image/tarexport/load.go (about)

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