From 71411324ebb9d25ec27b71abafde5ebe08d31211 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Sat, 28 Jul 2018 15:35:18 -0400 Subject: [PATCH] An image represents a manifest (and is a blob) and also references a config Our external API and internal structure is wrong. All images are manifests, and all manifests are blobs. In addition, a v2 schema 2 manifest references a blob that is a "config", not a "manifest". Corrected the internal code so that the image/manifest digest now shows up in blobs, renamed the internal field "config", and set the correct mime types and fixed tests. --- pkg/image/registry/imagestream/etcd/etcd.go | 6 +++- pkg/image/registry/imagestream/etcd/image.go | 30 +++++++++++++------- test/extended/images/layers.go | 4 +++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/pkg/image/registry/imagestream/etcd/etcd.go b/pkg/image/registry/imagestream/etcd/etcd.go index 9dec16caaaa7..216cc59b163d 100644 --- a/pkg/image/registry/imagestream/etcd/etcd.go +++ b/pkg/image/registry/imagestream/etcd/etcd.go @@ -192,7 +192,7 @@ func (r *LayersREST) Get(ctx context.Context, name string, options *metav1.GetOp isl.Blobs[layer.Name] = imageapi.ImageLayerData{LayerSize: &layer.LayerSize, MediaType: layer.MediaType} } } - if blob := entry.Manifest; blob != nil { + if blob := entry.Config; blob != nil { reference.Manifest = &blob.Name if _, ok := isl.Blobs[blob.Name]; !ok { if blob.LayerSize == 0 { @@ -203,6 +203,10 @@ func (r *LayersREST) Get(ctx context.Context, name string, options *metav1.GetOp } } } + // the image manifest is always a blob - schema2 images also have a config blob referenced from the manifest + if _, ok := isl.Blobs[item.Image]; !ok { + isl.Blobs[item.Image] = imageapi.ImageLayerData{MediaType: entry.MediaType} + } isl.Images[item.Image] = reference } } diff --git a/pkg/image/registry/imagestream/etcd/image.go b/pkg/image/registry/imagestream/etcd/image.go index 704371382bab..b6779a1578ab 100644 --- a/pkg/image/registry/imagestream/etcd/image.go +++ b/pkg/image/registry/imagestream/etcd/image.go @@ -121,9 +121,10 @@ func NewImageLayerIndex(lw ImageListWatch) ImageLayerIndex { return imageLayerIndex{informer: informer} } -// manifestFromImage attempts to find a manifest blob description from -// an image. Images older than schema2 in Docker do not have a manifest blob. -func manifestFromImage(image *imagev1.Image) *imagev1.ImageLayer { +// configFromImage attempts to find a config blob description from +// an image. Images older than schema2 in Docker do not have a config blob - the manifest +// has that data embedded. +func configFromImage(image *imagev1.Image) *imagev1.ImageLayer { if image.DockerImageManifestMediaType != "application/vnd.docker.distribution.manifest.v2+json" { return nil } @@ -134,7 +135,7 @@ func manifestFromImage(image *imagev1.Image) *imagev1.ImageLayer { } return &imagev1.ImageLayer{ Name: meta.ID, - MediaType: image.DockerImageManifestMediaType, + MediaType: "application/vnd.docker.container.image.v1+json", } } @@ -145,16 +146,22 @@ func manifestFromImage(image *imagev1.Image) *imagev1.ImageLayer { type ImageLayers struct { Name string ResourceVersion string - Manifest *imagev1.ImageLayer + MediaType string + Config *imagev1.ImageLayer Layers []imagev1.ImageLayer } func imageLayersForImage(image *imagev1.Image) *ImageLayers { + mediaType := image.DockerImageManifestMediaType + if len(mediaType) == 0 { + mediaType = "application/vnd.docker.distribution.manifest.v2+json" + } return &ImageLayers{ Name: image.Name, ResourceVersion: image.ResourceVersion, + MediaType: mediaType, + Config: configFromImage(image), Layers: image.DockerImageLayers, - Manifest: manifestFromImage(image), } } @@ -170,15 +177,16 @@ func (l *ImageLayers) DeepCopyObject() runtime.Object { layers = make([]imagev1.ImageLayer, len(l.Layers)) copy(layers, l.Layers) } - var manifest *imagev1.ImageLayer - if l.Manifest != nil { - copied := *l.Manifest - manifest = &copied + var config *imagev1.ImageLayer + if l.Config != nil { + copied := *l.Config + config = &copied } return &ImageLayers{ Name: l.Name, ResourceVersion: l.ResourceVersion, - Manifest: manifest, + MediaType: l.MediaType, + Config: config, Layers: layers, } } diff --git a/test/extended/images/layers.go b/test/extended/images/layers.go index a19d67c6df74..b7721094e5b9 100644 --- a/test/extended/images/layers.go +++ b/test/extended/images/layers.go @@ -72,10 +72,14 @@ var _ = g.Describe("[Feature:ImageLayers] Image layer subresource", func() { o.Expect(ok).To(o.BeTrue()) o.Expect(len(l.Layers)).To(o.BeNumerically(">", 0)) o.Expect(l.Manifest).ToNot(o.BeNil()) + o.Expect(layers.Blobs[*l.Manifest]).ToNot(o.BeNil()) + o.Expect(layers.Blobs[*l.Manifest].MediaType).To(o.Equal("application/vnd.docker.container.image.v1+json")) for _, layerID := range l.Layers { o.Expect(layers.Blobs).To(o.HaveKey(layerID)) o.Expect(layers.Blobs[layerID].MediaType).NotTo(o.BeEmpty()) } + o.Expect(layers.Blobs).To(o.HaveKey(image.Image.Name)) + o.Expect(layers.Blobs[image.Image.Name].MediaType).To(o.Equal("application/vnd.docker.distribution.manifest.v2+json")) if i == 0 { busyboxLayers = l.Layers }