Skip to content

Commit 9044499

Browse files
author
Michal Minář
committed
Fix missing sizes for manifest schema 1 images
Instead of filling metadata on image object in the registry, send manifest and config blobs to the master API and let it do the job. Signed-off-by: Michal Minář <[email protected]>
1 parent 97f4072 commit 9044499

9 files changed

+1518
-262
lines changed

pkg/dockerregistry/server/manifesthandler.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,24 @@ import (
99
"github.com/docker/distribution/manifest/schema1"
1010
"github.com/docker/distribution/manifest/schema2"
1111

12+
imageapi "github.com/openshift/origin/pkg/image/apis/image"
1213
imageapiv1 "github.com/openshift/origin/pkg/image/apis/image/v1"
1314
)
1415

1516
// A ManifestHandler defines a common set of operations on all versions of manifest schema.
1617
type ManifestHandler interface {
17-
// FillImageMetadata fills a given image with metadata parsed from manifest. It also corrects layer sizes
18-
// with blob sizes. Newer Docker client versions don't set layer sizes in the manifest schema 1 at all.
19-
// Origin master needs correct layer sizes for proper image quota support. That's why we need to fill the
20-
// metadata in the registry.
21-
FillImageMetadata(ctx context.Context, image *imageapiv1.Image) error
18+
// Config returns a blob with image configuration associated with the manifest. This applies only to
19+
// manifet schema 2.
20+
Config(ctx context.Context) ([]byte, error)
21+
22+
// Digest returns manifest's digest.
23+
Digest() (manifestDigest digest.Digest, err error)
24+
25+
// Layers returns a list of image layers.
26+
Layers(ctx context.Context) ([]imageapiv1.ImageLayer, error)
27+
28+
// Metadata returns image configuration in internal representation.
29+
Metadata(ctx context.Context) (*imageapi.DockerImage, error)
2230

2331
// Manifest returns a deserialized manifest object.
2432
Manifest() distribution.Manifest
@@ -27,11 +35,11 @@ type ManifestHandler interface {
2735
// signatures or an error if the information could not be fetched.
2836
Payload() (mediaType string, payload []byte, canonical []byte, err error)
2937

38+
// Signatures returns signature blobs embedded in the manifest. This applies only to manifest schema 1.
39+
Signatures(ctx context.Context) ([][]byte, error)
40+
3041
// Verify returns an error if the contained manifest is not valid or has missing dependencies.
3142
Verify(ctx context.Context, skipDependencyVerification bool) error
32-
33-
// Digest returns manifest's digest
34-
Digest() (manifestDigest digest.Digest, err error)
3543
}
3644

3745
// NewManifestHandler creates a manifest handler for the given manifest.

pkg/dockerregistry/server/manifestschema1handler.go

Lines changed: 95 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package server
22

33
import (
44
"encoding/json"
5+
"errors"
56
"fmt"
67
"path"
78

@@ -18,6 +19,9 @@ import (
1819
imageapiv1 "github.com/openshift/origin/pkg/image/apis/image/v1"
1920
)
2021

22+
// ErrNoManifestMetadata is an error informing about invalid manifest that lacks metadata.
23+
var ErrNoManifestMetadata = errors.New("no manifest metadata found")
24+
2125
func unmarshalManifestSchema1(content []byte, signatures [][]byte) (distribution.Manifest, error) {
2226
// prefer signatures from the manifest
2327
if _, err := libtrust.ParsePrettySignature(content, "signatures"); err == nil {
@@ -46,69 +50,97 @@ func unmarshalManifestSchema1(content []byte, signatures [][]byte) (distribution
4650
}
4751

4852
type manifestSchema1Handler struct {
49-
repo *repository
50-
manifest *schema1.SignedManifest
53+
repo *repository
54+
manifest *schema1.SignedManifest
55+
cachedLayers []imageapiv1.ImageLayer
5156
}
5257

5358
var _ ManifestHandler = &manifestSchema1Handler{}
5459

55-
func (h *manifestSchema1Handler) FillImageMetadata(ctx context.Context, image *imageapiv1.Image) error {
56-
signatures, err := h.manifest.Signatures()
57-
if err != nil {
58-
return err
60+
func (h *manifestSchema1Handler) Layers(ctx context.Context) ([]imageapiv1.ImageLayer, error) {
61+
if h.cachedLayers == nil {
62+
var sizeContainer = imageapi.DockerV1CompatibilityImageSize{}
63+
64+
layers := make([]imageapiv1.ImageLayer, len(h.manifest.FSLayers))
65+
for hi, li := 0, len(h.manifest.FSLayers)-1; hi < len(h.manifest.FSLayers) && li >= 0; hi, li = hi+1, li-1 {
66+
layer := &layers[li]
67+
sizeContainer.Size = 0
68+
if hi < len(h.manifest.History) {
69+
if err := json.Unmarshal([]byte(h.manifest.History[hi].V1Compatibility), &sizeContainer); err != nil {
70+
sizeContainer.Size = 0
71+
}
72+
}
73+
if err := h.updateLayerMetadata(ctx, layer, &h.manifest.FSLayers[hi], sizeContainer.Size); err != nil {
74+
return nil, err
75+
}
76+
}
77+
78+
h.cachedLayers = layers
5979
}
6080

61-
for _, signDigest := range signatures {
62-
image.DockerImageSignatures = append(image.DockerImageSignatures, signDigest)
81+
layers := make([]imageapiv1.ImageLayer, len(h.cachedLayers))
82+
for i, l := range h.cachedLayers {
83+
layers[i] = l
6384
}
6485

65-
refs := h.manifest.References()
86+
return layers, nil
87+
}
6688

67-
if err := imageMetadataFromManifest(image); err != nil {
68-
return fmt.Errorf("unable to fill image %s metadata: %v", image.Name, err)
89+
func (h *manifestSchema1Handler) Metadata(ctx context.Context) (*imageapi.DockerImage, error) {
90+
if len(h.manifest.History) == 0 {
91+
// should never have an empty history, but just in case...
92+
return nil, ErrNoManifestMetadata
6993
}
7094

71-
blobSet := sets.NewString()
72-
meta, ok := image.DockerImageMetadata.Object.(*imageapi.DockerImage)
73-
if !ok {
74-
return fmt.Errorf("image %q does not have metadata", image.Name)
95+
v1Metadata := imageapi.DockerV1CompatibilityImage{}
96+
if err := json.Unmarshal([]byte(h.manifest.History[0].V1Compatibility), &v1Metadata); err != nil {
97+
return nil, err
7598
}
76-
meta.Size = int64(0)
7799

78-
blobs := h.repo.Blobs(ctx)
79-
for i := range image.DockerImageLayers {
80-
layer := &image.DockerImageLayers[i]
81-
// DockerImageLayers represents h.manifest.Manifest.FSLayers in reversed order
82-
desc, err := blobs.Stat(ctx, refs[len(image.DockerImageLayers)-i-1].Digest)
83-
if err != nil {
84-
context.GetLogger(ctx).Errorf("failed to stat blob %s of image %s", layer.Name, image.DockerImageReference)
85-
return err
86-
}
87-
// The MediaType appeared in manifest schema v2. We need to fill it
88-
// manually in the old images if it is not already filled.
89-
if len(layer.MediaType) == 0 {
90-
if len(desc.MediaType) > 0 {
91-
layer.MediaType = desc.MediaType
92-
} else {
93-
layer.MediaType = schema1.MediaTypeManifestLayer
94-
}
95-
}
96-
layer.LayerSize = desc.Size
97-
// count empty layer just once (empty layer may actually have non-zero size)
98-
if !blobSet.Has(layer.Name) {
99-
meta.Size += desc.Size
100-
blobSet.Insert(layer.Name)
100+
var (
101+
dockerImageSize int64
102+
layerSet = sets.NewString()
103+
)
104+
105+
layers, err := h.Layers(ctx)
106+
if err != nil {
107+
return nil, err
108+
}
109+
for _, layer := range layers {
110+
if !layerSet.Has(layer.Name) {
111+
dockerImageSize += layer.LayerSize
112+
layerSet.Insert(layer.Name)
101113
}
102114
}
103-
image.DockerImageMetadata.Object = meta
104115

105-
return nil
116+
meta := &imageapi.DockerImage{}
117+
meta.ID = v1Metadata.ID
118+
meta.Parent = v1Metadata.Parent
119+
meta.Comment = v1Metadata.Comment
120+
meta.Created = v1Metadata.Created
121+
meta.Container = v1Metadata.Container
122+
meta.ContainerConfig = v1Metadata.ContainerConfig
123+
meta.DockerVersion = v1Metadata.DockerVersion
124+
meta.Author = v1Metadata.Author
125+
meta.Config = v1Metadata.Config
126+
meta.Architecture = v1Metadata.Architecture
127+
meta.Size = dockerImageSize
128+
129+
return meta, nil
130+
}
131+
132+
func (h *manifestSchema1Handler) Signatures(ctx context.Context) ([][]byte, error) {
133+
return h.manifest.Signatures()
106134
}
107135

108136
func (h *manifestSchema1Handler) Manifest() distribution.Manifest {
109137
return h.manifest
110138
}
111139

140+
func (h *manifestSchema1Handler) Config(ctx context.Context) ([]byte, error) {
141+
return nil, nil
142+
}
143+
112144
func (h *manifestSchema1Handler) Payload() (mediaType string, payload []byte, canonical []byte, err error) {
113145
mt, payload, err := h.manifest.Payload()
114146
return mt, payload, h.manifest.Canonical, err
@@ -187,3 +219,25 @@ func (h *manifestSchema1Handler) Verify(ctx context.Context, skipDependencyVerif
187219
func (h *manifestSchema1Handler) Digest() (digest.Digest, error) {
188220
return digest.FromBytes(h.manifest.Canonical), nil
189221
}
222+
223+
func (h *manifestSchema1Handler) updateLayerMetadata(
224+
ctx context.Context,
225+
layerMetadata *imageapiv1.ImageLayer,
226+
manifestLayer *schema1.FSLayer,
227+
size int64,
228+
) error {
229+
layerMetadata.Name = manifestLayer.BlobSum.String()
230+
layerMetadata.MediaType = schema1.MediaTypeManifestLayer
231+
if size > 0 {
232+
layerMetadata.LayerSize = size
233+
return nil
234+
}
235+
236+
desc, err := h.repo.Blobs(ctx).Stat(ctx, digest.Digest(layerMetadata.Name))
237+
if err != nil {
238+
context.GetLogger(ctx).Errorf("failed to stat blob %s", layerMetadata.Name)
239+
return err
240+
}
241+
layerMetadata.LayerSize = desc.Size
242+
return nil
243+
}

0 commit comments

Comments
 (0)