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