github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/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/docker/image"
    14  	"github.com/docker/docker/image/v1"
    15  	"github.com/docker/docker/layer"
    16  	"github.com/docker/docker/pkg/archive"
    17  	"github.com/docker/docker/pkg/system"
    18  	"github.com/docker/docker/reference"
    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  			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  	_, err = io.Copy(outStream, fs)
   219  	return err
   220  }
   221  
   222  func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Descriptor, error) {
   223  	img, err := s.is.Get(id)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  
   228  	if len(img.RootFS.DiffIDs) == 0 {
   229  		return nil, fmt.Errorf("empty export - not implemented")
   230  	}
   231  
   232  	var parent digest.Digest
   233  	var layers []string
   234  	var foreignSrcs map[layer.DiffID]distribution.Descriptor
   235  	for i := range img.RootFS.DiffIDs {
   236  		v1Img := image.V1Image{
   237  			Created: img.Created,
   238  		}
   239  		if i == len(img.RootFS.DiffIDs)-1 {
   240  			v1Img = img.V1Image
   241  		}
   242  		rootFS := *img.RootFS
   243  		rootFS.DiffIDs = rootFS.DiffIDs[:i+1]
   244  		v1ID, err := v1.CreateID(v1Img, rootFS.ChainID(), parent)
   245  		if err != nil {
   246  			return nil, err
   247  		}
   248  
   249  		v1Img.ID = v1ID.Hex()
   250  		if parent != "" {
   251  			v1Img.Parent = parent.Hex()
   252  		}
   253  
   254  		src, err := s.saveLayer(rootFS.ChainID(), v1Img, img.Created)
   255  		if err != nil {
   256  			return nil, err
   257  		}
   258  		layers = append(layers, v1Img.ID)
   259  		parent = v1ID
   260  		if src.Digest != "" {
   261  			if foreignSrcs == nil {
   262  				foreignSrcs = make(map[layer.DiffID]distribution.Descriptor)
   263  			}
   264  			foreignSrcs[img.RootFS.DiffIDs[i]] = src
   265  		}
   266  	}
   267  
   268  	configFile := filepath.Join(s.outDir, id.Digest().Hex()+".json")
   269  	if err := ioutil.WriteFile(configFile, img.RawJSON(), 0644); err != nil {
   270  		return nil, err
   271  	}
   272  	if err := system.Chtimes(configFile, img.Created, img.Created); err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	s.images[id].layers = layers
   277  	return foreignSrcs, nil
   278  }
   279  
   280  func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, createdTime time.Time) (distribution.Descriptor, error) {
   281  	if _, exists := s.savedLayers[legacyImg.ID]; exists {
   282  		return distribution.Descriptor{}, nil
   283  	}
   284  
   285  	outDir := filepath.Join(s.outDir, legacyImg.ID)
   286  	if err := os.Mkdir(outDir, 0755); err != nil {
   287  		return distribution.Descriptor{}, err
   288  	}
   289  
   290  	// todo: why is this version file here?
   291  	if err := ioutil.WriteFile(filepath.Join(outDir, legacyVersionFileName), []byte("1.0"), 0644); err != nil {
   292  		return distribution.Descriptor{}, err
   293  	}
   294  
   295  	imageConfig, err := json.Marshal(legacyImg)
   296  	if err != nil {
   297  		return distribution.Descriptor{}, err
   298  	}
   299  
   300  	if err := ioutil.WriteFile(filepath.Join(outDir, legacyConfigFileName), imageConfig, 0644); err != nil {
   301  		return distribution.Descriptor{}, err
   302  	}
   303  
   304  	// serialize filesystem
   305  	layerPath := filepath.Join(outDir, legacyLayerFileName)
   306  	l, err := s.ls.Get(id)
   307  	if err != nil {
   308  		return distribution.Descriptor{}, err
   309  	}
   310  	defer layer.ReleaseAndLog(s.ls, l)
   311  
   312  	if oldPath, exists := s.diffIDPaths[l.DiffID()]; exists {
   313  		relPath, err := filepath.Rel(outDir, oldPath)
   314  		if err != nil {
   315  			return distribution.Descriptor{}, err
   316  		}
   317  		if err := os.Symlink(relPath, layerPath); err != nil {
   318  			return distribution.Descriptor{}, errors.Wrap(err, "error creating symlink while saving layer")
   319  		}
   320  	} else {
   321  		// Use system.CreateSequential rather than os.Create. This ensures sequential
   322  		// file access on Windows to avoid eating into MM standby list.
   323  		// On Linux, this equates to a regular os.Create.
   324  		tarFile, err := system.CreateSequential(layerPath)
   325  		if err != nil {
   326  			return distribution.Descriptor{}, err
   327  		}
   328  		defer tarFile.Close()
   329  
   330  		arch, err := l.TarStream()
   331  		if err != nil {
   332  			return distribution.Descriptor{}, err
   333  		}
   334  		defer arch.Close()
   335  
   336  		if _, err := io.Copy(tarFile, arch); err != nil {
   337  			return distribution.Descriptor{}, err
   338  		}
   339  
   340  		for _, fname := range []string{"", legacyVersionFileName, legacyConfigFileName, legacyLayerFileName} {
   341  			// todo: maybe save layer created timestamp?
   342  			if err := system.Chtimes(filepath.Join(outDir, fname), createdTime, createdTime); err != nil {
   343  				return distribution.Descriptor{}, err
   344  			}
   345  		}
   346  
   347  		s.diffIDPaths[l.DiffID()] = layerPath
   348  	}
   349  	s.savedLayers[legacyImg.ID] = struct{}{}
   350  
   351  	var src distribution.Descriptor
   352  	if fs, ok := l.(distribution.Describable); ok {
   353  		src = fs.Descriptor()
   354  	}
   355  	return src, nil
   356  }