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