github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/image/tarexport/load.go (about)

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