Przeglądaj źródła

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 lat temu
rodzic
commit
ddd67b2535
2 zmienionych plików z 44 dodań i 31 usunięć
  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 {
 	refs     []reference.NamedTagged
-	layers   []string
+	layers   []digest.Digest
 	image    *image.Image
 	layerRef layer.Layer
 }
@@ -204,18 +204,18 @@ func (s *saveSession) save(outStream io.Writer) error {
 			if _, ok := reposLegacy[familiarName]; !ok {
 				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))
 		}
 
 		for _, l := range imageDescr.layers {
 			// IMPORTANT: We use path, not filepath here to ensure the layers
 			// 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{
-			Config:       id.Digest().Encoded() + ".json",
+			Config:       path.Join("blobs", id.Digest().Algorithm().String(), id.Digest().Encoded()),
 			RepoTags:     repoTags,
 			Layers:       layers,
 			LayerSources: foreignSrcs,
@@ -285,9 +285,9 @@ func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Desc
 	}
 
 	var parent digest.Digest
-	var layers []string
+	var layers []digest.Digest
 	var foreignSrcs map[layer.DiffID]distribution.Descriptor
-	for i := range img.RootFS.DiffIDs {
+	for i, diffID := range img.RootFS.DiffIDs {
 		v1Img := image.V1Image{
 			// This is for backward compatibility used for
 			// pre v1.9 docker.
@@ -313,7 +313,8 @@ func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Desc
 		if err != nil {
 			return nil, err
 		}
-		layers = append(layers, v1Img.ID)
+
+		layers = append(layers, digest.Digest(diffID))
 		parent = v1ID
 		if src.Digest != "" {
 			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 {
 		return nil, err
 	}
@@ -340,47 +355,46 @@ func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, creat
 		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)
 	if err != nil {
 		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
 	}
 
 	// serialize filesystem
-	layerPath := filepath.Join(outDir, legacyLayerFileName)
 	l, err := s.lss.Get(id)
 	if err != nil {
 		return distribution.Descriptor{}, err
 	}
+
+	lDgst := digest.Digest(l.DiffID())
+	layerPath := filepath.Join(outDir, lDgst.Algorithm().String(), lDgst.Encoded())
 	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
 		}
-		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
 		// 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)
 		if err != nil {
-			return distribution.Descriptor{}, err
+			return distribution.Descriptor{}, fmt.Errorf("error creating layer file: %w", err)
 		}
 		defer tarFile.Close()
 
@@ -394,16 +408,16 @@ func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, creat
 			return distribution.Descriptor{}, err
 		}
 
-		for _, fname := range []string{"", legacyVersionFileName, legacyConfigFileName, legacyLayerFileName} {
+		for _, fname := range []string{outDir, configPath, layerPath} {
 			// 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
 			}
 		}
 
 		s.diffIDPaths[l.DiffID()] = layerPath
+		s.savedLayers[legacyImg.ID] = struct{}{}
 	}
-	s.savedLayers[legacyImg.ID] = struct{}{}
 
 	var src distribution.Descriptor
 	if fs, ok := l.(distribution.Describable); ok {

+ 0 - 1
image/tarexport/tarexport.go

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