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