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