gopkg.in/docker/docker.v20@v20.10.27/image/tarexport/save.go (about)

     1  package tarexport // import "github.com/docker/docker/image/tarexport"
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  	"runtime"
    11  	"time"
    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/system"
    20  	digest "github.com/opencontainers/go-digest"
    21  	"github.com/pkg/errors"
    22  )
    23  
    24  type imageDescriptor struct {
    25  	refs     []reference.NamedTagged
    26  	layers   []string
    27  	image    *image.Image
    28  	layerRef layer.Layer
    29  }
    30  
    31  type saveSession struct {
    32  	*tarexporter
    33  	outDir      string
    34  	images      map[image.ID]*imageDescriptor
    35  	savedLayers map[string]struct{}
    36  	diffIDPaths map[layer.DiffID]string // cache every diffID blob to avoid duplicates
    37  }
    38  
    39  func (l *tarexporter) Save(names []string, outStream io.Writer) error {
    40  	images, err := l.parseNames(names)
    41  	if err != nil {
    42  		return err
    43  	}
    44  
    45  	// Release all the image top layer references
    46  	defer l.releaseLayerReferences(images)
    47  	return (&saveSession{tarexporter: l, images: images}).save(outStream)
    48  }
    49  
    50  // parseNames will parse the image names to a map which contains image.ID to *imageDescriptor.
    51  // Each imageDescriptor holds an image top layer reference named 'layerRef'. It is taken here, should be released later.
    52  func (l *tarexporter) parseNames(names []string) (desc map[image.ID]*imageDescriptor, rErr error) {
    53  	imgDescr := make(map[image.ID]*imageDescriptor)
    54  	defer func() {
    55  		if rErr != nil {
    56  			l.releaseLayerReferences(imgDescr)
    57  		}
    58  	}()
    59  
    60  	addAssoc := func(id image.ID, ref reference.Named) error {
    61  		if _, ok := imgDescr[id]; !ok {
    62  			descr := &imageDescriptor{}
    63  			if err := l.takeLayerReference(id, descr); err != nil {
    64  				return err
    65  			}
    66  			imgDescr[id] = descr
    67  		}
    68  
    69  		if ref != nil {
    70  			if _, ok := ref.(reference.Canonical); ok {
    71  				return nil
    72  			}
    73  			tagged, ok := reference.TagNameOnly(ref).(reference.NamedTagged)
    74  			if !ok {
    75  				return nil
    76  			}
    77  
    78  			for _, t := range imgDescr[id].refs {
    79  				if tagged.String() == t.String() {
    80  					return nil
    81  				}
    82  			}
    83  			imgDescr[id].refs = append(imgDescr[id].refs, tagged)
    84  		}
    85  		return nil
    86  	}
    87  
    88  	for _, name := range names {
    89  		ref, err := reference.ParseAnyReference(name)
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  		namedRef, ok := ref.(reference.Named)
    94  		if !ok {
    95  			// Check if digest ID reference
    96  			if digested, ok := ref.(reference.Digested); ok {
    97  				id := image.IDFromDigest(digested.Digest())
    98  				if err := addAssoc(id, nil); err != nil {
    99  					return nil, err
   100  				}
   101  				continue
   102  			}
   103  			return nil, errors.Errorf("invalid reference: %v", name)
   104  		}
   105  
   106  		if reference.FamiliarName(namedRef) == string(digest.Canonical) {
   107  			imgID, err := l.is.Search(name)
   108  			if err != nil {
   109  				return nil, err
   110  			}
   111  			if err := addAssoc(imgID, nil); err != nil {
   112  				return nil, err
   113  			}
   114  			continue
   115  		}
   116  		if reference.IsNameOnly(namedRef) {
   117  			assocs := l.rs.ReferencesByName(namedRef)
   118  			for _, assoc := range assocs {
   119  				if err := addAssoc(image.IDFromDigest(assoc.ID), assoc.Ref); err != nil {
   120  					return nil, err
   121  				}
   122  			}
   123  			if len(assocs) == 0 {
   124  				imgID, err := l.is.Search(name)
   125  				if err != nil {
   126  					return nil, err
   127  				}
   128  				if err := addAssoc(imgID, nil); err != nil {
   129  					return nil, err
   130  				}
   131  			}
   132  			continue
   133  		}
   134  		id, err := l.rs.Get(namedRef)
   135  		if err != nil {
   136  			return nil, err
   137  		}
   138  		if err := addAssoc(image.IDFromDigest(id), namedRef); err != nil {
   139  			return nil, err
   140  		}
   141  
   142  	}
   143  	return imgDescr, nil
   144  }
   145  
   146  // takeLayerReference will take/Get the image top layer reference
   147  func (l *tarexporter) takeLayerReference(id image.ID, imgDescr *imageDescriptor) error {
   148  	img, err := l.is.Get(id)
   149  	if err != nil {
   150  		return err
   151  	}
   152  	imgDescr.image = img
   153  	topLayerID := img.RootFS.ChainID()
   154  	if topLayerID == "" {
   155  		return nil
   156  	}
   157  	os := img.OS
   158  	if os == "" {
   159  		os = runtime.GOOS
   160  	}
   161  	if !system.IsOSSupported(os) {
   162  		return fmt.Errorf("os %q is not supported", os)
   163  	}
   164  	layer, err := l.lss[os].Get(topLayerID)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	imgDescr.layerRef = layer
   169  	return nil
   170  }
   171  
   172  // releaseLayerReferences will release all the image top layer references
   173  func (l *tarexporter) releaseLayerReferences(imgDescr map[image.ID]*imageDescriptor) error {
   174  	for _, descr := range imgDescr {
   175  		if descr.layerRef != nil {
   176  			os := descr.image.OS
   177  			if os == "" {
   178  				os = runtime.GOOS
   179  			}
   180  			l.lss[os].Release(descr.layerRef)
   181  		}
   182  	}
   183  	return nil
   184  }
   185  
   186  func (s *saveSession) save(outStream io.Writer) error {
   187  	s.savedLayers = make(map[string]struct{})
   188  	s.diffIDPaths = make(map[layer.DiffID]string)
   189  
   190  	// get image json
   191  	tempDir, err := os.MkdirTemp("", "docker-export-")
   192  	if err != nil {
   193  		return err
   194  	}
   195  	defer os.RemoveAll(tempDir)
   196  
   197  	s.outDir = tempDir
   198  	reposLegacy := make(map[string]map[string]string)
   199  
   200  	var manifest []manifestItem
   201  	var parentLinks []parentLink
   202  
   203  	for id, imageDescr := range s.images {
   204  		foreignSrcs, err := s.saveImage(id)
   205  		if err != nil {
   206  			return err
   207  		}
   208  
   209  		var repoTags []string
   210  		var layers []string
   211  
   212  		for _, ref := range imageDescr.refs {
   213  			familiarName := reference.FamiliarName(ref)
   214  			if _, ok := reposLegacy[familiarName]; !ok {
   215  				reposLegacy[familiarName] = make(map[string]string)
   216  			}
   217  			reposLegacy[familiarName][ref.Tag()] = imageDescr.layers[len(imageDescr.layers)-1]
   218  			repoTags = append(repoTags, reference.FamiliarString(ref))
   219  		}
   220  
   221  		for _, l := range imageDescr.layers {
   222  			// IMPORTANT: We use path, not filepath here to ensure the layers
   223  			// in the manifest use Unix-style forward-slashes. Otherwise, a
   224  			// Linux image saved from LCOW won't be able to be imported on
   225  			// LCOL.
   226  			layers = append(layers, path.Join(l, legacyLayerFileName))
   227  		}
   228  
   229  		manifest = append(manifest, manifestItem{
   230  			Config:       id.Digest().Hex() + ".json",
   231  			RepoTags:     repoTags,
   232  			Layers:       layers,
   233  			LayerSources: foreignSrcs,
   234  		})
   235  
   236  		parentID, _ := s.is.GetParent(id)
   237  		parentLinks = append(parentLinks, parentLink{id, parentID})
   238  		s.tarexporter.loggerImgEvent.LogImageEvent(id.String(), id.String(), "save")
   239  	}
   240  
   241  	for i, p := range validatedParentLinks(parentLinks) {
   242  		if p.parentID != "" {
   243  			manifest[i].Parent = p.parentID
   244  		}
   245  	}
   246  
   247  	if len(reposLegacy) > 0 {
   248  		reposFile := filepath.Join(tempDir, legacyRepositoriesFileName)
   249  		rf, err := os.OpenFile(reposFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
   250  		if err != nil {
   251  			return err
   252  		}
   253  
   254  		if err := json.NewEncoder(rf).Encode(reposLegacy); err != nil {
   255  			rf.Close()
   256  			return err
   257  		}
   258  
   259  		rf.Close()
   260  
   261  		if err := system.Chtimes(reposFile, time.Unix(0, 0), time.Unix(0, 0)); err != nil {
   262  			return err
   263  		}
   264  	}
   265  
   266  	manifestFileName := filepath.Join(tempDir, manifestFileName)
   267  	f, err := os.OpenFile(manifestFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
   268  	if err != nil {
   269  		return err
   270  	}
   271  
   272  	if err := json.NewEncoder(f).Encode(manifest); err != nil {
   273  		f.Close()
   274  		return err
   275  	}
   276  
   277  	f.Close()
   278  
   279  	if err := system.Chtimes(manifestFileName, time.Unix(0, 0), time.Unix(0, 0)); err != nil {
   280  		return err
   281  	}
   282  
   283  	fs, err := archive.Tar(tempDir, archive.Uncompressed)
   284  	if err != nil {
   285  		return err
   286  	}
   287  	defer fs.Close()
   288  
   289  	_, err = io.Copy(outStream, fs)
   290  	return err
   291  }
   292  
   293  func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Descriptor, error) {
   294  	img := s.images[id].image
   295  	if len(img.RootFS.DiffIDs) == 0 {
   296  		return nil, fmt.Errorf("empty export - not implemented")
   297  	}
   298  
   299  	var parent digest.Digest
   300  	var layers []string
   301  	var foreignSrcs map[layer.DiffID]distribution.Descriptor
   302  	for i := range img.RootFS.DiffIDs {
   303  		v1Img := image.V1Image{
   304  			// This is for backward compatibility used for
   305  			// pre v1.9 docker.
   306  			Created: time.Unix(0, 0),
   307  		}
   308  		if i == len(img.RootFS.DiffIDs)-1 {
   309  			v1Img = img.V1Image
   310  		}
   311  		rootFS := *img.RootFS
   312  		rootFS.DiffIDs = rootFS.DiffIDs[:i+1]
   313  		v1ID, err := v1.CreateID(v1Img, rootFS.ChainID(), parent)
   314  		if err != nil {
   315  			return nil, err
   316  		}
   317  
   318  		v1Img.ID = v1ID.Hex()
   319  		if parent != "" {
   320  			v1Img.Parent = parent.Hex()
   321  		}
   322  
   323  		v1Img.OS = img.OS
   324  		src, err := s.saveLayer(rootFS.ChainID(), v1Img, img.Created)
   325  		if err != nil {
   326  			return nil, err
   327  		}
   328  		layers = append(layers, v1Img.ID)
   329  		parent = v1ID
   330  		if src.Digest != "" {
   331  			if foreignSrcs == nil {
   332  				foreignSrcs = make(map[layer.DiffID]distribution.Descriptor)
   333  			}
   334  			foreignSrcs[img.RootFS.DiffIDs[i]] = src
   335  		}
   336  	}
   337  
   338  	configFile := filepath.Join(s.outDir, id.Digest().Hex()+".json")
   339  	if err := os.WriteFile(configFile, img.RawJSON(), 0644); err != nil {
   340  		return nil, err
   341  	}
   342  	if err := system.Chtimes(configFile, img.Created, img.Created); err != nil {
   343  		return nil, err
   344  	}
   345  
   346  	s.images[id].layers = layers
   347  	return foreignSrcs, nil
   348  }
   349  
   350  func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, createdTime time.Time) (distribution.Descriptor, error) {
   351  	if _, exists := s.savedLayers[legacyImg.ID]; exists {
   352  		return distribution.Descriptor{}, nil
   353  	}
   354  
   355  	outDir := filepath.Join(s.outDir, legacyImg.ID)
   356  	if err := os.Mkdir(outDir, 0755); err != nil {
   357  		return distribution.Descriptor{}, err
   358  	}
   359  
   360  	// todo: why is this version file here?
   361  	if err := os.WriteFile(filepath.Join(outDir, legacyVersionFileName), []byte("1.0"), 0644); err != nil {
   362  		return distribution.Descriptor{}, err
   363  	}
   364  
   365  	imageConfig, err := json.Marshal(legacyImg)
   366  	if err != nil {
   367  		return distribution.Descriptor{}, err
   368  	}
   369  
   370  	if err := os.WriteFile(filepath.Join(outDir, legacyConfigFileName), imageConfig, 0644); err != nil {
   371  		return distribution.Descriptor{}, err
   372  	}
   373  
   374  	// serialize filesystem
   375  	layerPath := filepath.Join(outDir, legacyLayerFileName)
   376  	operatingSystem := legacyImg.OS
   377  	if operatingSystem == "" {
   378  		operatingSystem = runtime.GOOS
   379  	}
   380  	l, err := s.lss[operatingSystem].Get(id)
   381  	if err != nil {
   382  		return distribution.Descriptor{}, err
   383  	}
   384  	defer layer.ReleaseAndLog(s.lss[operatingSystem], l)
   385  
   386  	if oldPath, exists := s.diffIDPaths[l.DiffID()]; exists {
   387  		relPath, err := filepath.Rel(outDir, oldPath)
   388  		if err != nil {
   389  			return distribution.Descriptor{}, err
   390  		}
   391  		if err := os.Symlink(relPath, layerPath); err != nil {
   392  			return distribution.Descriptor{}, errors.Wrap(err, "error creating symlink while saving layer")
   393  		}
   394  	} else {
   395  		// Use system.CreateSequential rather than os.Create. This ensures sequential
   396  		// file access on Windows to avoid eating into MM standby list.
   397  		// On Linux, this equates to a regular os.Create.
   398  		tarFile, err := system.CreateSequential(layerPath)
   399  		if err != nil {
   400  			return distribution.Descriptor{}, err
   401  		}
   402  		defer tarFile.Close()
   403  
   404  		arch, err := l.TarStream()
   405  		if err != nil {
   406  			return distribution.Descriptor{}, err
   407  		}
   408  		defer arch.Close()
   409  
   410  		if _, err := io.Copy(tarFile, arch); err != nil {
   411  			return distribution.Descriptor{}, err
   412  		}
   413  
   414  		for _, fname := range []string{"", legacyVersionFileName, legacyConfigFileName, legacyLayerFileName} {
   415  			// todo: maybe save layer created timestamp?
   416  			if err := system.Chtimes(filepath.Join(outDir, fname), createdTime, createdTime); err != nil {
   417  				return distribution.Descriptor{}, err
   418  			}
   419  		}
   420  
   421  		s.diffIDPaths[l.DiffID()] = layerPath
   422  	}
   423  	s.savedLayers[legacyImg.ID] = struct{}{}
   424  
   425  	var src distribution.Descriptor
   426  	if fs, ok := l.(distribution.Describable); ok {
   427  		src = fs.Descriptor()
   428  	}
   429  	return src, nil
   430  }