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