github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/image/tarexport/load.go (about)

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