github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/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  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"runtime"
    12  
    13  	"github.com/containerd/containerd/platforms"
    14  	"github.com/docker/distribution"
    15  	"github.com/docker/distribution/reference"
    16  	"github.com/docker/docker/image"
    17  	v1 "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/system"
    25  	"github.com/moby/sys/symlink"
    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 := os.MkdirTemp("", "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 := os.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 := img.OS
    94  		if os == "" {
    95  			os = runtime.GOOS
    96  		}
    97  		if runtime.GOOS == "windows" {
    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.lss[os].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.lss[os], 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 string, 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.lss[os].(layer.DescribableStore); ok {
   209  		return ds.RegisterWithDescriptor(inflatedLayerData, rootFS.ChainID(), foreignSrc)
   210  	}
   211  	return l.lss[os].Register(inflatedLayerData, rootFS.ChainID())
   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 := os.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 := os.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  	if img.OS == "" {
   306  		img.OS = runtime.GOOS
   307  	}
   308  
   309  	var parentID image.ID
   310  	if img.Parent != "" {
   311  		for {
   312  			var loaded bool
   313  			if parentID, loaded = loadedMap[img.Parent]; !loaded {
   314  				if err := l.legacyLoadImage(img.Parent, sourceDir, loadedMap, progressOutput); err != nil {
   315  					return err
   316  				}
   317  			} else {
   318  				break
   319  			}
   320  		}
   321  	}
   322  
   323  	// todo: try to connect with migrate code
   324  	rootFS := image.NewRootFS()
   325  	var history []image.History
   326  
   327  	if parentID != "" {
   328  		parentImg, err := l.is.Get(parentID)
   329  		if err != nil {
   330  			return err
   331  		}
   332  
   333  		rootFS = parentImg.RootFS
   334  		history = parentImg.History
   335  	}
   336  
   337  	layerPath, err := safePath(sourceDir, filepath.Join(oldID, legacyLayerFileName))
   338  	if err != nil {
   339  		return err
   340  	}
   341  	newLayer, err := l.loadLayer(layerPath, *rootFS, oldID, img.OS, distribution.Descriptor{}, progressOutput)
   342  	if err != nil {
   343  		return err
   344  	}
   345  	rootFS.Append(newLayer.DiffID())
   346  
   347  	h, err := v1.HistoryFromConfig(imageJSON, false)
   348  	if err != nil {
   349  		return err
   350  	}
   351  	history = append(history, h)
   352  
   353  	config, err := v1.MakeConfigFromV1Config(imageJSON, rootFS, history)
   354  	if err != nil {
   355  		return err
   356  	}
   357  	imgID, err := l.is.Create(config)
   358  	if err != nil {
   359  		return err
   360  	}
   361  
   362  	metadata, err := l.lss[img.OS].Release(newLayer)
   363  	layer.LogReleaseMetadata(metadata)
   364  	if err != nil {
   365  		return err
   366  	}
   367  
   368  	if parentID != "" {
   369  		if err := l.is.SetParent(imgID, parentID); err != nil {
   370  			return err
   371  		}
   372  	}
   373  
   374  	loadedMap[oldID] = imgID
   375  	return nil
   376  }
   377  
   378  func safePath(base, path string) (string, error) {
   379  	return symlink.FollowSymlinkInScope(filepath.Join(base, path), base)
   380  }
   381  
   382  type parentLink struct {
   383  	id, parentID image.ID
   384  }
   385  
   386  func validatedParentLinks(pl []parentLink) (ret []parentLink) {
   387  mainloop:
   388  	for i, p := range pl {
   389  		ret = append(ret, p)
   390  		for _, p2 := range pl {
   391  			if p2.id == p.parentID && p2.id != p.id {
   392  				continue mainloop
   393  			}
   394  		}
   395  		ret[i].parentID = ""
   396  	}
   397  	return
   398  }
   399  
   400  func checkValidParent(img, parent *image.Image) bool {
   401  	if len(img.History) == 0 && len(parent.History) == 0 {
   402  		return true // having history is not mandatory
   403  	}
   404  	if len(img.History)-len(parent.History) != 1 {
   405  		return false
   406  	}
   407  	for i, h := range parent.History {
   408  		if !reflect.DeepEqual(h, img.History[i]) {
   409  			return false
   410  		}
   411  	}
   412  	return true
   413  }
   414  
   415  func checkCompatibleOS(imageOS string) error {
   416  	// always compatible if the images OS matches the host OS; also match an empty image OS
   417  	if imageOS == runtime.GOOS || imageOS == "" {
   418  		return nil
   419  	}
   420  	// On non-Windows hosts, for compatibility, fail if the image is Windows.
   421  	if runtime.GOOS != "windows" && imageOS == "windows" {
   422  		return fmt.Errorf("cannot load %s image on %s", imageOS, runtime.GOOS)
   423  	}
   424  
   425  	p, err := platforms.Parse(imageOS)
   426  	if err != nil {
   427  		return err
   428  	}
   429  
   430  	return system.ValidatePlatform(p)
   431  }