Browse Source

Make image save more like the OCI layout for blobs

This moves the blobs around so they follow the OCI spec.
Note that because docker reads paths from the manifest.json inside the
tar this is not a breaking change.

This does, however, remove the old layer "VERSION" file which had a big
"why is this even here" in the code comments. I suspect it does not
matter at all even for really old versions of Docker. In any case it is
a useless file for any even relatively modern version of Docker.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
Brian Goff 2 năm trước cách đây
mục cha
commit
ddd67b2535
2 tập tin đã thay đổi với 44 bổ sung31 xóa
  1. 44 30
      image/tarexport/save.go
  2. 0 1
      image/tarexport/tarexport.go

+ 44 - 30
image/tarexport/save.go

@@ -23,7 +23,7 @@ import (
 
 
 type imageDescriptor struct {
 type imageDescriptor struct {
 	refs     []reference.NamedTagged
 	refs     []reference.NamedTagged
-	layers   []string
+	layers   []digest.Digest
 	image    *image.Image
 	image    *image.Image
 	layerRef layer.Layer
 	layerRef layer.Layer
 }
 }
@@ -204,18 +204,18 @@ func (s *saveSession) save(outStream io.Writer) error {
 			if _, ok := reposLegacy[familiarName]; !ok {
 			if _, ok := reposLegacy[familiarName]; !ok {
 				reposLegacy[familiarName] = make(map[string]string)
 				reposLegacy[familiarName] = make(map[string]string)
 			}
 			}
-			reposLegacy[familiarName][ref.Tag()] = imageDescr.layers[len(imageDescr.layers)-1]
+			reposLegacy[familiarName][ref.Tag()] = imageDescr.layers[len(imageDescr.layers)-1].Encoded()
 			repoTags = append(repoTags, reference.FamiliarString(ref))
 			repoTags = append(repoTags, reference.FamiliarString(ref))
 		}
 		}
 
 
 		for _, l := range imageDescr.layers {
 		for _, l := range imageDescr.layers {
 			// IMPORTANT: We use path, not filepath here to ensure the layers
 			// IMPORTANT: We use path, not filepath here to ensure the layers
 			// in the manifest use Unix-style forward-slashes.
 			// in the manifest use Unix-style forward-slashes.
-			layers = append(layers, path.Join(l, legacyLayerFileName))
+			layers = append(layers, path.Join("blobs", l.Algorithm().String(), l.Encoded()))
 		}
 		}
 
 
 		manifest = append(manifest, manifestItem{
 		manifest = append(manifest, manifestItem{
-			Config:       id.Digest().Encoded() + ".json",
+			Config:       path.Join("blobs", id.Digest().Algorithm().String(), id.Digest().Encoded()),
 			RepoTags:     repoTags,
 			RepoTags:     repoTags,
 			Layers:       layers,
 			Layers:       layers,
 			LayerSources: foreignSrcs,
 			LayerSources: foreignSrcs,
@@ -285,9 +285,9 @@ func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Desc
 	}
 	}
 
 
 	var parent digest.Digest
 	var parent digest.Digest
-	var layers []string
+	var layers []digest.Digest
 	var foreignSrcs map[layer.DiffID]distribution.Descriptor
 	var foreignSrcs map[layer.DiffID]distribution.Descriptor
-	for i := range img.RootFS.DiffIDs {
+	for i, diffID := range img.RootFS.DiffIDs {
 		v1Img := image.V1Image{
 		v1Img := image.V1Image{
 			// This is for backward compatibility used for
 			// This is for backward compatibility used for
 			// pre v1.9 docker.
 			// pre v1.9 docker.
@@ -313,7 +313,8 @@ func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Desc
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
-		layers = append(layers, v1Img.ID)
+
+		layers = append(layers, digest.Digest(diffID))
 		parent = v1ID
 		parent = v1ID
 		if src.Digest != "" {
 		if src.Digest != "" {
 			if foreignSrcs == nil {
 			if foreignSrcs == nil {
@@ -323,7 +324,21 @@ func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Desc
 		}
 		}
 	}
 	}
 
 
-	configFile := filepath.Join(s.outDir, id.Digest().Encoded()+".json")
+	data := img.RawJSON()
+	dgst := digest.FromBytes(data)
+
+	blobDir := filepath.Join(s.outDir, "blobs", dgst.Algorithm().String())
+	if err := os.MkdirAll(blobDir, 0o755); err != nil {
+		return nil, err
+	}
+	if err := system.Chtimes(blobDir, img.Created, img.Created); err != nil {
+		return nil, err
+	}
+	if err := system.Chtimes(filepath.Dir(blobDir), img.Created, img.Created); err != nil {
+		return nil, err
+	}
+
+	configFile := filepath.Join(blobDir, dgst.Encoded())
 	if err := os.WriteFile(configFile, img.RawJSON(), 0o644); err != nil {
 	if err := os.WriteFile(configFile, img.RawJSON(), 0o644); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -340,47 +355,46 @@ func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, creat
 		return distribution.Descriptor{}, nil
 		return distribution.Descriptor{}, nil
 	}
 	}
 
 
-	outDir := filepath.Join(s.outDir, legacyImg.ID)
-	if err := os.Mkdir(outDir, 0755); err != nil {
-		return distribution.Descriptor{}, err
-	}
-
-	// todo: why is this version file here?
-	if err := os.WriteFile(filepath.Join(outDir, legacyVersionFileName), []byte("1.0"), 0644); err != nil {
-		return distribution.Descriptor{}, err
-	}
+	outDir := filepath.Join(s.outDir, "blobs")
 
 
 	imageConfig, err := json.Marshal(legacyImg)
 	imageConfig, err := json.Marshal(legacyImg)
 	if err != nil {
 	if err != nil {
 		return distribution.Descriptor{}, err
 		return distribution.Descriptor{}, err
 	}
 	}
 
 
-	if err := os.WriteFile(filepath.Join(outDir, legacyConfigFileName), imageConfig, 0644); err != nil {
+	cfgDgst := digest.FromBytes(imageConfig)
+	configPath := filepath.Join(outDir, cfgDgst.Algorithm().String(), cfgDgst.Encoded())
+	if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
+		return distribution.Descriptor{}, fmt.Errorf("could not create layer dir parent: %w", err)
+	}
+
+	if err := os.WriteFile(configPath, imageConfig, 0644); err != nil {
 		return distribution.Descriptor{}, err
 		return distribution.Descriptor{}, err
 	}
 	}
 
 
 	// serialize filesystem
 	// serialize filesystem
-	layerPath := filepath.Join(outDir, legacyLayerFileName)
 	l, err := s.lss.Get(id)
 	l, err := s.lss.Get(id)
 	if err != nil {
 	if err != nil {
 		return distribution.Descriptor{}, err
 		return distribution.Descriptor{}, err
 	}
 	}
+
+	lDgst := digest.Digest(l.DiffID())
+	layerPath := filepath.Join(outDir, lDgst.Algorithm().String(), lDgst.Encoded())
 	defer layer.ReleaseAndLog(s.lss, l)
 	defer layer.ReleaseAndLog(s.lss, l)
 
 
-	if oldPath, exists := s.diffIDPaths[l.DiffID()]; exists {
-		relPath, err := filepath.Rel(outDir, oldPath)
-		if err != nil {
+	if _, err = os.Stat(layerPath); err != nil {
+		if !os.IsNotExist(err) {
 			return distribution.Descriptor{}, err
 			return distribution.Descriptor{}, err
 		}
 		}
-		if err := os.Symlink(relPath, layerPath); err != nil {
-			return distribution.Descriptor{}, errors.Wrap(err, "error creating symlink while saving layer")
-		}
-	} else {
+
 		// We use sequential file access to avoid depleting the standby list on
 		// We use sequential file access to avoid depleting the standby list on
 		// Windows. On Linux, this equates to a regular os.Create.
 		// Windows. On Linux, this equates to a regular os.Create.
+		if err := os.MkdirAll(filepath.Dir(layerPath), 0755); err != nil {
+			return distribution.Descriptor{}, fmt.Errorf("could not create layer dir parent: %w", err)
+		}
 		tarFile, err := sequential.Create(layerPath)
 		tarFile, err := sequential.Create(layerPath)
 		if err != nil {
 		if err != nil {
-			return distribution.Descriptor{}, err
+			return distribution.Descriptor{}, fmt.Errorf("error creating layer file: %w", err)
 		}
 		}
 		defer tarFile.Close()
 		defer tarFile.Close()
 
 
@@ -394,16 +408,16 @@ func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, creat
 			return distribution.Descriptor{}, err
 			return distribution.Descriptor{}, err
 		}
 		}
 
 
-		for _, fname := range []string{"", legacyVersionFileName, legacyConfigFileName, legacyLayerFileName} {
+		for _, fname := range []string{outDir, configPath, layerPath} {
 			// todo: maybe save layer created timestamp?
 			// todo: maybe save layer created timestamp?
-			if err := system.Chtimes(filepath.Join(outDir, fname), createdTime, createdTime); err != nil {
+			if err := system.Chtimes(fname, createdTime, createdTime); err != nil {
 				return distribution.Descriptor{}, err
 				return distribution.Descriptor{}, err
 			}
 			}
 		}
 		}
 
 
 		s.diffIDPaths[l.DiffID()] = layerPath
 		s.diffIDPaths[l.DiffID()] = layerPath
+		s.savedLayers[legacyImg.ID] = struct{}{}
 	}
 	}
-	s.savedLayers[legacyImg.ID] = struct{}{}
 
 
 	var src distribution.Descriptor
 	var src distribution.Descriptor
 	if fs, ok := l.(distribution.Describable); ok {
 	if fs, ok := l.(distribution.Describable); ok {

+ 0 - 1
image/tarexport/tarexport.go

@@ -11,7 +11,6 @@ const (
 	manifestFileName           = "manifest.json"
 	manifestFileName           = "manifest.json"
 	legacyLayerFileName        = "layer.tar"
 	legacyLayerFileName        = "layer.tar"
 	legacyConfigFileName       = "json"
 	legacyConfigFileName       = "json"
-	legacyVersionFileName      = "VERSION"
 	legacyRepositoriesFileName = "repositories"
 	legacyRepositoriesFileName = "repositories"
 )
 )