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