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:
parent
6e98a7f2c9
commit
ddd67b2535
2 changed files with 44 additions and 31 deletions
|
@ -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 {
|
||||
|
|
|
@ -11,7 +11,6 @@ const (
|
|||
manifestFileName = "manifest.json"
|
||||
legacyLayerFileName = "layer.tar"
|
||||
legacyConfigFileName = "json"
|
||||
legacyVersionFileName = "VERSION"
|
||||
legacyRepositoriesFileName = "repositories"
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue