github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/image/tarexport/save.go (about)

     1  package tarexport // import "github.com/Prakhar-Agarwal-byte/moby/image/tarexport"
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  	"time"
    11  
    12  	"github.com/containerd/containerd/images"
    13  	"github.com/distribution/reference"
    14  	"github.com/docker/distribution"
    15  	"github.com/Prakhar-Agarwal-byte/moby/api/types/events"
    16  	"github.com/Prakhar-Agarwal-byte/moby/image"
    17  	v1 "github.com/Prakhar-Agarwal-byte/moby/image/v1"
    18  	"github.com/Prakhar-Agarwal-byte/moby/layer"
    19  	"github.com/Prakhar-Agarwal-byte/moby/pkg/archive"
    20  	"github.com/Prakhar-Agarwal-byte/moby/pkg/system"
    21  	"github.com/moby/sys/sequential"
    22  	"github.com/opencontainers/go-digest"
    23  	"github.com/opencontainers/image-spec/specs-go"
    24  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    25  	"github.com/pkg/errors"
    26  )
    27  
    28  type imageDescriptor struct {
    29  	refs     []reference.NamedTagged
    30  	layers   []digest.Digest
    31  	image    *image.Image
    32  	layerRef layer.Layer
    33  }
    34  
    35  type saveSession struct {
    36  	*tarexporter
    37  	outDir      string
    38  	images      map[image.ID]*imageDescriptor
    39  	savedLayers map[string]struct{}
    40  	diffIDPaths map[layer.DiffID]string // cache every diffID blob to avoid duplicates
    41  }
    42  
    43  func (l *tarexporter) Save(names []string, outStream io.Writer) error {
    44  	images, err := l.parseNames(names)
    45  	if err != nil {
    46  		return err
    47  	}
    48  
    49  	// Release all the image top layer references
    50  	defer l.releaseLayerReferences(images)
    51  	return (&saveSession{tarexporter: l, images: images}).save(outStream)
    52  }
    53  
    54  // parseNames will parse the image names to a map which contains image.ID to *imageDescriptor.
    55  // Each imageDescriptor holds an image top layer reference named 'layerRef'. It is taken here, should be released later.
    56  func (l *tarexporter) parseNames(names []string) (desc map[image.ID]*imageDescriptor, rErr error) {
    57  	imgDescr := make(map[image.ID]*imageDescriptor)
    58  	defer func() {
    59  		if rErr != nil {
    60  			l.releaseLayerReferences(imgDescr)
    61  		}
    62  	}()
    63  
    64  	addAssoc := func(id image.ID, ref reference.Named) error {
    65  		if _, ok := imgDescr[id]; !ok {
    66  			descr := &imageDescriptor{}
    67  			if err := l.takeLayerReference(id, descr); err != nil {
    68  				return err
    69  			}
    70  			imgDescr[id] = descr
    71  		}
    72  
    73  		if ref != nil {
    74  			if _, ok := ref.(reference.Canonical); ok {
    75  				return nil
    76  			}
    77  			tagged, ok := reference.TagNameOnly(ref).(reference.NamedTagged)
    78  			if !ok {
    79  				return nil
    80  			}
    81  
    82  			for _, t := range imgDescr[id].refs {
    83  				if tagged.String() == t.String() {
    84  					return nil
    85  				}
    86  			}
    87  			imgDescr[id].refs = append(imgDescr[id].refs, tagged)
    88  		}
    89  		return nil
    90  	}
    91  
    92  	for _, name := range names {
    93  		ref, err := reference.ParseAnyReference(name)
    94  		if err != nil {
    95  			return nil, err
    96  		}
    97  		namedRef, ok := ref.(reference.Named)
    98  		if !ok {
    99  			// Check if digest ID reference
   100  			if digested, ok := ref.(reference.Digested); ok {
   101  				if err := addAssoc(image.ID(digested.Digest()), nil); err != nil {
   102  					return nil, err
   103  				}
   104  				continue
   105  			}
   106  			return nil, errors.Errorf("invalid reference: %v", name)
   107  		}
   108  
   109  		if reference.FamiliarName(namedRef) == string(digest.Canonical) {
   110  			imgID, err := l.is.Search(name)
   111  			if err != nil {
   112  				return nil, err
   113  			}
   114  			if err := addAssoc(imgID, nil); err != nil {
   115  				return nil, err
   116  			}
   117  			continue
   118  		}
   119  		if reference.IsNameOnly(namedRef) {
   120  			assocs := l.rs.ReferencesByName(namedRef)
   121  			for _, assoc := range assocs {
   122  				if err := addAssoc(image.ID(assoc.ID), assoc.Ref); err != nil {
   123  					return nil, err
   124  				}
   125  			}
   126  			if len(assocs) == 0 {
   127  				imgID, err := l.is.Search(name)
   128  				if err != nil {
   129  					return nil, err
   130  				}
   131  				if err := addAssoc(imgID, nil); err != nil {
   132  					return nil, err
   133  				}
   134  			}
   135  			continue
   136  		}
   137  		id, err := l.rs.Get(namedRef)
   138  		if err != nil {
   139  			return nil, err
   140  		}
   141  		if err := addAssoc(image.ID(id), namedRef); err != nil {
   142  			return nil, err
   143  		}
   144  	}
   145  	return imgDescr, nil
   146  }
   147  
   148  // takeLayerReference will take/Get the image top layer reference
   149  func (l *tarexporter) takeLayerReference(id image.ID, imgDescr *imageDescriptor) error {
   150  	img, err := l.is.Get(id)
   151  	if err != nil {
   152  		return err
   153  	}
   154  	if err := image.CheckOS(img.OperatingSystem()); err != nil {
   155  		return fmt.Errorf("os %q is not supported", img.OperatingSystem())
   156  	}
   157  	imgDescr.image = img
   158  	topLayerID := img.RootFS.ChainID()
   159  	if topLayerID == "" {
   160  		return nil
   161  	}
   162  	layer, err := l.lss.Get(topLayerID)
   163  	if err != nil {
   164  		return err
   165  	}
   166  	imgDescr.layerRef = layer
   167  	return nil
   168  }
   169  
   170  // releaseLayerReferences will release all the image top layer references
   171  func (l *tarexporter) releaseLayerReferences(imgDescr map[image.ID]*imageDescriptor) error {
   172  	for _, descr := range imgDescr {
   173  		if descr.layerRef != nil {
   174  			l.lss.Release(descr.layerRef)
   175  		}
   176  	}
   177  	return nil
   178  }
   179  
   180  func (s *saveSession) save(outStream io.Writer) error {
   181  	s.savedLayers = make(map[string]struct{})
   182  	s.diffIDPaths = make(map[layer.DiffID]string)
   183  
   184  	// get image json
   185  	tempDir, err := os.MkdirTemp("", "docker-export-")
   186  	if err != nil {
   187  		return err
   188  	}
   189  	defer os.RemoveAll(tempDir)
   190  
   191  	s.outDir = tempDir
   192  	reposLegacy := make(map[string]map[string]string)
   193  
   194  	var manifest []manifestItem
   195  	var parentLinks []parentLink
   196  
   197  	var manifestDescriptors []ocispec.Descriptor
   198  
   199  	for id, imageDescr := range s.images {
   200  		foreignSrcs, err := s.saveImage(id)
   201  		if err != nil {
   202  			return err
   203  		}
   204  
   205  		var (
   206  			repoTags []string
   207  			layers   []string
   208  			foreign  = make([]ocispec.Descriptor, 0, len(foreignSrcs))
   209  		)
   210  
   211  		for _, desc := range foreignSrcs {
   212  			foreign = append(foreign, ocispec.Descriptor{
   213  				MediaType:   desc.MediaType,
   214  				Digest:      desc.Digest,
   215  				Size:        desc.Size,
   216  				URLs:        desc.URLs,
   217  				Annotations: desc.Annotations,
   218  				Platform:    desc.Platform,
   219  			})
   220  		}
   221  
   222  		imgPlat := imageDescr.image.Platform()
   223  
   224  		m := ocispec.Manifest{
   225  			Versioned: specs.Versioned{
   226  				SchemaVersion: 2,
   227  			},
   228  			MediaType: ocispec.MediaTypeImageManifest,
   229  			Config: ocispec.Descriptor{
   230  				MediaType: ocispec.MediaTypeImageConfig,
   231  				Digest:    digest.Digest(imageDescr.image.ID()),
   232  				Size:      int64(len(imageDescr.image.RawJSON())),
   233  				Platform:  &imgPlat,
   234  			},
   235  			Layers: foreign,
   236  		}
   237  
   238  		data, err := json.Marshal(m)
   239  		if err != nil {
   240  			return errors.Wrap(err, "error marshaling manifest")
   241  		}
   242  		dgst := digest.FromBytes(data)
   243  
   244  		mFile := filepath.Join(s.outDir, ocispec.ImageBlobsDir, dgst.Algorithm().String(), dgst.Encoded())
   245  		if err := os.MkdirAll(filepath.Dir(mFile), 0o755); err != nil {
   246  			return errors.Wrap(err, "error creating blob directory")
   247  		}
   248  		if err := system.Chtimes(filepath.Dir(mFile), time.Unix(0, 0), time.Unix(0, 0)); err != nil {
   249  			return errors.Wrap(err, "error setting blob directory timestamps")
   250  		}
   251  		if err := os.WriteFile(mFile, data, 0o644); err != nil {
   252  			return errors.Wrap(err, "error writing oci manifest file")
   253  		}
   254  		if err := system.Chtimes(mFile, time.Unix(0, 0), time.Unix(0, 0)); err != nil {
   255  			return errors.Wrap(err, "error setting blob directory timestamps")
   256  		}
   257  		size := int64(len(data))
   258  
   259  		for _, ref := range imageDescr.refs {
   260  			familiarName := reference.FamiliarName(ref)
   261  			if _, ok := reposLegacy[familiarName]; !ok {
   262  				reposLegacy[familiarName] = make(map[string]string)
   263  			}
   264  			reposLegacy[familiarName][ref.Tag()] = imageDescr.layers[len(imageDescr.layers)-1].Encoded()
   265  			repoTags = append(repoTags, reference.FamiliarString(ref))
   266  
   267  			manifestDescriptors = append(manifestDescriptors, ocispec.Descriptor{
   268  				MediaType: ocispec.MediaTypeImageManifest,
   269  				Digest:    dgst,
   270  				Size:      size,
   271  				Platform:  m.Config.Platform,
   272  				Annotations: map[string]string{
   273  					images.AnnotationImageName: ref.String(),
   274  					ocispec.AnnotationRefName:  ref.Tag(),
   275  				},
   276  			})
   277  		}
   278  
   279  		for _, l := range imageDescr.layers {
   280  			// IMPORTANT: We use path, not filepath here to ensure the layers
   281  			// in the manifest use Unix-style forward-slashes.
   282  			layers = append(layers, path.Join(ocispec.ImageBlobsDir, l.Algorithm().String(), l.Encoded()))
   283  		}
   284  
   285  		manifest = append(manifest, manifestItem{
   286  			Config:       path.Join(ocispec.ImageBlobsDir, id.Digest().Algorithm().String(), id.Digest().Encoded()),
   287  			RepoTags:     repoTags,
   288  			Layers:       layers,
   289  			LayerSources: foreignSrcs,
   290  		})
   291  
   292  		parentID, _ := s.is.GetParent(id)
   293  		parentLinks = append(parentLinks, parentLink{id, parentID})
   294  		s.tarexporter.loggerImgEvent.LogImageEvent(id.String(), id.String(), events.ActionSave)
   295  	}
   296  
   297  	for i, p := range validatedParentLinks(parentLinks) {
   298  		if p.parentID != "" {
   299  			manifest[i].Parent = p.parentID
   300  		}
   301  	}
   302  
   303  	if len(reposLegacy) > 0 {
   304  		reposFile := filepath.Join(tempDir, legacyRepositoriesFileName)
   305  		rf, err := os.OpenFile(reposFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
   306  		if err != nil {
   307  			return err
   308  		}
   309  
   310  		if err := json.NewEncoder(rf).Encode(reposLegacy); err != nil {
   311  			rf.Close()
   312  			return err
   313  		}
   314  
   315  		rf.Close()
   316  
   317  		if err := system.Chtimes(reposFile, time.Unix(0, 0), time.Unix(0, 0)); err != nil {
   318  			return err
   319  		}
   320  	}
   321  
   322  	manifestPath := filepath.Join(tempDir, manifestFileName)
   323  	f, err := os.OpenFile(manifestPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
   324  	if err != nil {
   325  		return err
   326  	}
   327  
   328  	if err := json.NewEncoder(f).Encode(manifest); err != nil {
   329  		f.Close()
   330  		return err
   331  	}
   332  
   333  	f.Close()
   334  
   335  	if err := system.Chtimes(manifestPath, time.Unix(0, 0), time.Unix(0, 0)); err != nil {
   336  		return err
   337  	}
   338  
   339  	const ociLayoutContent = `{"imageLayoutVersion": "` + ocispec.ImageLayoutVersion + `"}`
   340  	layoutPath := filepath.Join(tempDir, ocispec.ImageLayoutFile)
   341  	if err := os.WriteFile(layoutPath, []byte(ociLayoutContent), 0o644); err != nil {
   342  		return errors.Wrap(err, "error writing oci layout file")
   343  	}
   344  	if err := system.Chtimes(layoutPath, time.Unix(0, 0), time.Unix(0, 0)); err != nil {
   345  		return errors.Wrap(err, "error setting oci layout file timestamps")
   346  	}
   347  
   348  	data, err := json.Marshal(ocispec.Index{
   349  		Versioned: specs.Versioned{
   350  			SchemaVersion: 2,
   351  		},
   352  		MediaType: ocispec.MediaTypeImageIndex,
   353  		Manifests: manifestDescriptors,
   354  	})
   355  	if err != nil {
   356  		return errors.Wrap(err, "error marshaling oci index")
   357  	}
   358  
   359  	idxFile := filepath.Join(s.outDir, ocispec.ImageIndexFile)
   360  	if err := os.WriteFile(idxFile, data, 0o644); err != nil {
   361  		return errors.Wrap(err, "error writing oci index file")
   362  	}
   363  
   364  	fs, err := archive.Tar(tempDir, archive.Uncompressed)
   365  	if err != nil {
   366  		return err
   367  	}
   368  	defer fs.Close()
   369  
   370  	_, err = io.Copy(outStream, fs)
   371  	return err
   372  }
   373  
   374  func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Descriptor, error) {
   375  	img := s.images[id].image
   376  	if len(img.RootFS.DiffIDs) == 0 {
   377  		return nil, fmt.Errorf("empty export - not implemented")
   378  	}
   379  
   380  	var parent digest.Digest
   381  	var layers []digest.Digest
   382  	var foreignSrcs map[layer.DiffID]distribution.Descriptor
   383  	for i, diffID := range img.RootFS.DiffIDs {
   384  		v1ImgCreated := time.Unix(0, 0)
   385  		v1Img := image.V1Image{
   386  			// This is for backward compatibility used for
   387  			// pre v1.9 docker.
   388  			Created: &v1ImgCreated,
   389  		}
   390  		if i == len(img.RootFS.DiffIDs)-1 {
   391  			v1Img = img.V1Image
   392  		}
   393  		rootFS := *img.RootFS
   394  		rootFS.DiffIDs = rootFS.DiffIDs[:i+1]
   395  		v1ID, err := v1.CreateID(v1Img, rootFS.ChainID(), parent)
   396  		if err != nil {
   397  			return nil, err
   398  		}
   399  
   400  		v1Img.ID = v1ID.Encoded()
   401  		if parent != "" {
   402  			v1Img.Parent = parent.Encoded()
   403  		}
   404  
   405  		v1Img.OS = img.OS
   406  		src, err := s.saveLayer(rootFS.ChainID(), v1Img, img.Created)
   407  		if err != nil {
   408  			return nil, err
   409  		}
   410  
   411  		layers = append(layers, digest.Digest(diffID))
   412  		parent = v1ID
   413  		if src.Digest != "" {
   414  			if foreignSrcs == nil {
   415  				foreignSrcs = make(map[layer.DiffID]distribution.Descriptor)
   416  			}
   417  			foreignSrcs[img.RootFS.DiffIDs[i]] = src
   418  		}
   419  	}
   420  
   421  	data := img.RawJSON()
   422  	dgst := digest.FromBytes(data)
   423  
   424  	blobDir := filepath.Join(s.outDir, ocispec.ImageBlobsDir, dgst.Algorithm().String())
   425  	if err := os.MkdirAll(blobDir, 0o755); err != nil {
   426  		return nil, err
   427  	}
   428  	if img.Created != nil {
   429  		if err := system.Chtimes(blobDir, *img.Created, *img.Created); err != nil {
   430  			return nil, err
   431  		}
   432  		if err := system.Chtimes(filepath.Dir(blobDir), *img.Created, *img.Created); err != nil {
   433  			return nil, err
   434  		}
   435  	}
   436  
   437  	configFile := filepath.Join(blobDir, dgst.Encoded())
   438  	if err := os.WriteFile(configFile, img.RawJSON(), 0o644); err != nil {
   439  		return nil, err
   440  	}
   441  	if img.Created != nil {
   442  		if err := system.Chtimes(configFile, *img.Created, *img.Created); err != nil {
   443  			return nil, err
   444  		}
   445  	}
   446  
   447  	s.images[id].layers = layers
   448  	return foreignSrcs, nil
   449  }
   450  
   451  func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, createdTime *time.Time) (distribution.Descriptor, error) {
   452  	if _, exists := s.savedLayers[legacyImg.ID]; exists {
   453  		return distribution.Descriptor{}, nil
   454  	}
   455  
   456  	outDir := filepath.Join(s.outDir, ocispec.ImageBlobsDir)
   457  
   458  	imageConfig, err := json.Marshal(legacyImg)
   459  	if err != nil {
   460  		return distribution.Descriptor{}, err
   461  	}
   462  
   463  	cfgDgst := digest.FromBytes(imageConfig)
   464  	configPath := filepath.Join(outDir, cfgDgst.Algorithm().String(), cfgDgst.Encoded())
   465  	if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
   466  		return distribution.Descriptor{}, errors.Wrap(err, "could not create layer dir parent")
   467  	}
   468  
   469  	if err := os.WriteFile(configPath, imageConfig, 0o644); err != nil {
   470  		return distribution.Descriptor{}, err
   471  	}
   472  
   473  	// serialize filesystem
   474  	l, err := s.lss.Get(id)
   475  	if err != nil {
   476  		return distribution.Descriptor{}, err
   477  	}
   478  
   479  	lDgst := digest.Digest(l.DiffID())
   480  	layerPath := filepath.Join(outDir, lDgst.Algorithm().String(), lDgst.Encoded())
   481  	defer layer.ReleaseAndLog(s.lss, l)
   482  
   483  	if _, err = os.Stat(layerPath); err != nil {
   484  		if !os.IsNotExist(err) {
   485  			return distribution.Descriptor{}, err
   486  		}
   487  
   488  		// We use sequential file access to avoid depleting the standby list on
   489  		// Windows. On Linux, this equates to a regular os.Create.
   490  		if err := os.MkdirAll(filepath.Dir(layerPath), 0o755); err != nil {
   491  			return distribution.Descriptor{}, errors.Wrap(err, "could not create layer dir parent")
   492  		}
   493  		tarFile, err := sequential.Create(layerPath)
   494  		if err != nil {
   495  			return distribution.Descriptor{}, errors.Wrap(err, "error creating layer file")
   496  		}
   497  		defer tarFile.Close()
   498  
   499  		arch, err := l.TarStream()
   500  		if err != nil {
   501  			return distribution.Descriptor{}, err
   502  		}
   503  		defer arch.Close()
   504  
   505  		if _, err := io.Copy(tarFile, arch); err != nil {
   506  			return distribution.Descriptor{}, err
   507  		}
   508  
   509  		if createdTime != nil {
   510  			for _, fname := range []string{outDir, configPath, layerPath} {
   511  				// todo: maybe save layer created timestamp?
   512  				if err := system.Chtimes(fname, *createdTime, *createdTime); err != nil {
   513  					return distribution.Descriptor{}, errors.Wrap(err, "could not set layer timestamp")
   514  				}
   515  			}
   516  		}
   517  
   518  		s.diffIDPaths[l.DiffID()] = layerPath
   519  		s.savedLayers[legacyImg.ID] = struct{}{}
   520  	}
   521  
   522  	var src distribution.Descriptor
   523  	if fs, ok := l.(distribution.Describable); ok {
   524  		src = fs.Descriptor()
   525  	}
   526  	return src, nil
   527  }