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>
This commit is contained in:
Brian Goff 2022-12-06 22:54:03 +00:00
parent 6e98a7f2c9
commit ddd67b2535
2 changed files with 44 additions and 31 deletions

View file

@ -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 {

View file

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