github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/image/tarexport/load.go (about)

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