github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/image/tarexport/save.go (about)

     1  package tarexport
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"time"
    11  
    12  	"github.com/docker/distribution"
    13  	"github.com/docker/distribution/digest"
    14  	"github.com/docker/docker/image"
    15  	"github.com/docker/docker/image/v1"
    16  	"github.com/docker/docker/layer"
    17  	"github.com/docker/docker/pkg/archive"
    18  	"github.com/docker/docker/pkg/system"
    19  	"github.com/docker/docker/reference"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  type imageDescriptor struct {
    24  	refs   []reference.NamedTagged
    25  	layers []string
    26  }
    27  
    28  type saveSession struct {
    29  	*tarexporter
    30  	outDir      string
    31  	images      map[image.ID]*imageDescriptor
    32  	savedLayers map[string]struct{}
    33  	diffIDPaths map[layer.DiffID]string // cache every diffID blob to avoid duplicates
    34  }
    35  
    36  func (l *tarexporter) Save(names []string, outStream io.Writer) error {
    37  	images, err := l.parseNames(names)
    38  	if err != nil {
    39  		return err
    40  	}
    41  
    42  	return (&saveSession{tarexporter: l, images: images}).save(outStream)
    43  }
    44  
    45  func (l *tarexporter) parseNames(names []string) (map[image.ID]*imageDescriptor, error) {
    46  	imgDescr := make(map[image.ID]*imageDescriptor)
    47  
    48  	addAssoc := func(id image.ID, ref reference.Named) {
    49  		if _, ok := imgDescr[id]; !ok {
    50  			imgDescr[id] = &imageDescriptor{}
    51  		}
    52  
    53  		if ref != nil {
    54  			var tagged reference.NamedTagged
    55  			if _, ok := ref.(reference.Canonical); ok {
    56  				return
    57  			}
    58  			var ok bool
    59  			if tagged, ok = ref.(reference.NamedTagged); !ok {
    60  				var err error
    61  				if tagged, err = reference.WithTag(ref, reference.DefaultTag); err != nil {
    62  					return
    63  				}
    64  			}
    65  
    66  			for _, t := range imgDescr[id].refs {
    67  				if tagged.String() == t.String() {
    68  					return
    69  				}
    70  			}
    71  			imgDescr[id].refs = append(imgDescr[id].refs, tagged)
    72  		}
    73  	}
    74  
    75  	for _, name := range names {
    76  		id, ref, err := reference.ParseIDOrReference(name)
    77  		if err != nil {
    78  			return nil, err
    79  		}
    80  		if id != "" {
    81  			_, err := l.is.Get(image.IDFromDigest(id))
    82  			if err != nil {
    83  				return nil, err
    84  			}
    85  			addAssoc(image.IDFromDigest(id), nil)
    86  			continue
    87  		}
    88  		if ref.Name() == string(digest.Canonical) {
    89  			imgID, err := l.is.Search(name)
    90  			if err != nil {
    91  				return nil, err
    92  			}
    93  			addAssoc(imgID, nil)
    94  			continue
    95  		}
    96  		if reference.IsNameOnly(ref) {
    97  			assocs := l.rs.ReferencesByName(ref)
    98  			for _, assoc := range assocs {
    99  				addAssoc(image.IDFromDigest(assoc.ID), assoc.Ref)
   100  			}
   101  			if len(assocs) == 0 {
   102  				imgID, err := l.is.Search(name)
   103  				if err != nil {
   104  					return nil, err
   105  				}
   106  				addAssoc(imgID, nil)
   107  			}
   108  			continue
   109  		}
   110  		id, err = l.rs.Get(ref)
   111  		if err != nil {
   112  			return nil, err
   113  		}
   114  		addAssoc(image.IDFromDigest(id), ref)
   115  
   116  	}
   117  	return imgDescr, nil
   118  }
   119  
   120  func (s *saveSession) save(outStream io.Writer) error {
   121  	s.savedLayers = make(map[string]struct{})
   122  	s.diffIDPaths = make(map[layer.DiffID]string)
   123  
   124  	// get image json
   125  	tempDir, err := ioutil.TempDir("", "docker-export-")
   126  	if err != nil {
   127  		return err
   128  	}
   129  	defer os.RemoveAll(tempDir)
   130  
   131  	s.outDir = tempDir
   132  	reposLegacy := make(map[string]map[string]string)
   133  
   134  	var manifest []manifestItem
   135  	var parentLinks []parentLink
   136  
   137  	for id, imageDescr := range s.images {
   138  		foreignSrcs, err := s.saveImage(id)
   139  		if err != nil {
   140  			return err
   141  		}
   142  
   143  		var repoTags []string
   144  		var layers []string
   145  
   146  		for _, ref := range imageDescr.refs {
   147  			if _, ok := reposLegacy[ref.Name()]; !ok {
   148  				reposLegacy[ref.Name()] = make(map[string]string)
   149  			}
   150  			reposLegacy[ref.Name()][ref.Tag()] = imageDescr.layers[len(imageDescr.layers)-1]
   151  			repoTags = append(repoTags, ref.String())
   152  		}
   153  
   154  		for _, l := range imageDescr.layers {
   155  			layers = append(layers, filepath.Join(l, legacyLayerFileName))
   156  		}
   157  
   158  		manifest = append(manifest, manifestItem{
   159  			Config:       id.Digest().Hex() + ".json",
   160  			RepoTags:     repoTags,
   161  			Layers:       layers,
   162  			LayerSources: foreignSrcs,
   163  		})
   164  
   165  		parentID, _ := s.is.GetParent(id)
   166  		parentLinks = append(parentLinks, parentLink{id, parentID})
   167  		s.tarexporter.loggerImgEvent.LogImageEvent(id.String(), id.String(), "save")
   168  	}
   169  
   170  	for i, p := range validatedParentLinks(parentLinks) {
   171  		if p.parentID != "" {
   172  			manifest[i].Parent = p.parentID
   173  		}
   174  	}
   175  
   176  	if len(reposLegacy) > 0 {
   177  		reposFile := filepath.Join(tempDir, legacyRepositoriesFileName)
   178  		rf, err := os.OpenFile(reposFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
   179  		if err != nil {
   180  			return err
   181  		}
   182  
   183  		if err := json.NewEncoder(rf).Encode(reposLegacy); err != nil {
   184  			rf.Close()
   185  			return err
   186  		}
   187  
   188  		rf.Close()
   189  
   190  		if err := system.Chtimes(reposFile, time.Unix(0, 0), time.Unix(0, 0)); err != nil {
   191  			return err
   192  		}
   193  	}
   194  
   195  	manifestFileName := filepath.Join(tempDir, manifestFileName)
   196  	f, err := os.OpenFile(manifestFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	if err := json.NewEncoder(f).Encode(manifest); err != nil {
   202  		f.Close()
   203  		return err
   204  	}
   205  
   206  	f.Close()
   207  
   208  	if err := system.Chtimes(manifestFileName, time.Unix(0, 0), time.Unix(0, 0)); err != nil {
   209  		return err
   210  	}
   211  
   212  	fs, err := archive.Tar(tempDir, archive.Uncompressed)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	defer fs.Close()
   217  
   218  	if _, err := io.Copy(outStream, fs); err != nil {
   219  		return err
   220  	}
   221  	return nil
   222  }
   223  
   224  func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Descriptor, error) {
   225  	img, err := s.is.Get(id)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	if len(img.RootFS.DiffIDs) == 0 {
   231  		return nil, fmt.Errorf("empty export - not implemented")
   232  	}
   233  
   234  	var parent digest.Digest
   235  	var layers []string
   236  	var foreignSrcs map[layer.DiffID]distribution.Descriptor
   237  	for i := range img.RootFS.DiffIDs {
   238  		v1Img := image.V1Image{
   239  			Created: img.Created,
   240  		}
   241  		if i == len(img.RootFS.DiffIDs)-1 {
   242  			v1Img = img.V1Image
   243  		}
   244  		rootFS := *img.RootFS
   245  		rootFS.DiffIDs = rootFS.DiffIDs[:i+1]
   246  		v1ID, err := v1.CreateID(v1Img, rootFS.ChainID(), parent)
   247  		if err != nil {
   248  			return nil, err
   249  		}
   250  
   251  		v1Img.ID = v1ID.Hex()
   252  		if parent != "" {
   253  			v1Img.Parent = parent.Hex()
   254  		}
   255  
   256  		src, err := s.saveLayer(rootFS.ChainID(), v1Img, img.Created)
   257  		if err != nil {
   258  			return nil, err
   259  		}
   260  		layers = append(layers, v1Img.ID)
   261  		parent = v1ID
   262  		if src.Digest != "" {
   263  			if foreignSrcs == nil {
   264  				foreignSrcs = make(map[layer.DiffID]distribution.Descriptor)
   265  			}
   266  			foreignSrcs[img.RootFS.DiffIDs[i]] = src
   267  		}
   268  	}
   269  
   270  	configFile := filepath.Join(s.outDir, id.Digest().Hex()+".json")
   271  	if err := ioutil.WriteFile(configFile, img.RawJSON(), 0644); err != nil {
   272  		return nil, err
   273  	}
   274  	if err := system.Chtimes(configFile, img.Created, img.Created); err != nil {
   275  		return nil, err
   276  	}
   277  
   278  	s.images[id].layers = layers
   279  	return foreignSrcs, nil
   280  }
   281  
   282  func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, createdTime time.Time) (distribution.Descriptor, error) {
   283  	if _, exists := s.savedLayers[legacyImg.ID]; exists {
   284  		return distribution.Descriptor{}, nil
   285  	}
   286  
   287  	outDir := filepath.Join(s.outDir, legacyImg.ID)
   288  	if err := os.Mkdir(outDir, 0755); err != nil {
   289  		return distribution.Descriptor{}, err
   290  	}
   291  
   292  	// todo: why is this version file here?
   293  	if err := ioutil.WriteFile(filepath.Join(outDir, legacyVersionFileName), []byte("1.0"), 0644); err != nil {
   294  		return distribution.Descriptor{}, err
   295  	}
   296  
   297  	imageConfig, err := json.Marshal(legacyImg)
   298  	if err != nil {
   299  		return distribution.Descriptor{}, err
   300  	}
   301  
   302  	if err := ioutil.WriteFile(filepath.Join(outDir, legacyConfigFileName), imageConfig, 0644); err != nil {
   303  		return distribution.Descriptor{}, err
   304  	}
   305  
   306  	// serialize filesystem
   307  	layerPath := filepath.Join(outDir, legacyLayerFileName)
   308  	l, err := s.ls.Get(id)
   309  	if err != nil {
   310  		return distribution.Descriptor{}, err
   311  	}
   312  	defer layer.ReleaseAndLog(s.ls, l)
   313  
   314  	if oldPath, exists := s.diffIDPaths[l.DiffID()]; exists {
   315  		relPath, err := filepath.Rel(outDir, oldPath)
   316  		if err != nil {
   317  			return distribution.Descriptor{}, err
   318  		}
   319  		if err := os.Symlink(relPath, layerPath); err != nil {
   320  			return distribution.Descriptor{}, errors.Wrap(err, "error creating symlink while saving layer")
   321  		}
   322  	} else {
   323  		// Use system.CreateSequential rather than os.Create. This ensures sequential
   324  		// file access on Windows to avoid eating into MM standby list.
   325  		// On Linux, this equates to a regular os.Create.
   326  		tarFile, err := system.CreateSequential(layerPath)
   327  		if err != nil {
   328  			return distribution.Descriptor{}, err
   329  		}
   330  		defer tarFile.Close()
   331  
   332  		arch, err := l.TarStream()
   333  		if err != nil {
   334  			return distribution.Descriptor{}, err
   335  		}
   336  		defer arch.Close()
   337  
   338  		if _, err := io.Copy(tarFile, arch); err != nil {
   339  			return distribution.Descriptor{}, err
   340  		}
   341  
   342  		for _, fname := range []string{"", legacyVersionFileName, legacyConfigFileName, legacyLayerFileName} {
   343  			// todo: maybe save layer created timestamp?
   344  			if err := system.Chtimes(filepath.Join(outDir, fname), createdTime, createdTime); err != nil {
   345  				return distribution.Descriptor{}, err
   346  			}
   347  		}
   348  
   349  		s.diffIDPaths[l.DiffID()] = layerPath
   350  	}
   351  	s.savedLayers[legacyImg.ID] = struct{}{}
   352  
   353  	var src distribution.Descriptor
   354  	if fs, ok := l.(distribution.Describable); ok {
   355  		src = fs.Descriptor()
   356  	}
   357  	return src, nil
   358  }