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