Skip to content

Commit 56f50e6

Browse files
committed
Add support for image indexes
1 parent 08fde4f commit 56f50e6

File tree

6 files changed

+103
-45
lines changed

6 files changed

+103
-45
lines changed

pkg/image/apiserver/importer/image.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ package importer
33
import (
44
"encoding/json"
55
"fmt"
6-
76
"github.com/distribution/distribution/v3"
8-
"github.com/distribution/distribution/v3/manifest/manifestlist"
97
"github.com/distribution/distribution/v3/registry/api/errcode"
108
godigest "github.com/opencontainers/go-digest"
119

@@ -56,10 +54,7 @@ func schema2OrOCIToImage(manifest distribution.Manifest, imageConfig []byte, d g
5654
return image, nil
5755
}
5856

59-
func manifestListToImage(
60-
manifest *manifestlist.DeserializedManifestList,
61-
d godigest.Digest,
62-
) (*imageapi.Image, error) {
57+
func manifestToImage(manifest distribution.Manifest, d godigest.Digest) (*imageapi.Image, error) {
6358
mediatype, payload, err := manifest.Payload()
6459
if err != nil {
6560
return nil, err
@@ -88,7 +83,7 @@ func manifestListToImage(
8883
DockerImageManifestMediaType: mediatype,
8984
}
9085

91-
for _, manifest := range manifest.Manifests {
86+
for _, manifest := range manifest.References() {
9287
m := imageapi.ImageManifest{
9388
Digest: manifest.Digest.String(),
9489
MediaType: manifest.MediaType,

pkg/image/apiserver/importer/image_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func TestManifestListToImageConversion(t *testing.T) {
1515
t.Fatal(err)
1616
}
1717

18-
image, err := manifestListToImage(manifestList, "sha256:5020d54ec2de60c4e187128b5a03adda261a7fe78c9c500ffd24ff4af476fb41")
18+
image, err := manifestToImage(manifestList, "sha256:5020d54ec2de60c4e187128b5a03adda261a7fe78c9c500ffd24ff4af476fb41")
1919
if err != nil {
2020
t.Fatal(err)
2121
}

pkg/image/apiserver/importer/importer.go

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -668,14 +668,15 @@ func (imp *ImageStreamImporter) getManifest(
668668
return nil, nil, nil, utilerrors.NewAggregate(errs)
669669
}
670670

671-
func manifestFromManifestList(
671+
func selectManifestForPlatform(
672672
ctx context.Context,
673-
manifestList *manifestlist.DeserializedManifestList,
673+
manifest distribution.Manifest,
674674
ref reference.Named,
675675
s distribution.ManifestService,
676676
preferArch, preferOS string,
677677
) (distribution.Manifest, godigest.Digest, error) {
678-
if len(manifestList.Manifests) == 0 {
678+
descriptors := manifest.References()
679+
if len(descriptors) == 0 {
679680
return nil, "", fmt.Errorf("no manifests in manifest list")
680681
}
681682

@@ -687,7 +688,7 @@ func manifestFromManifestList(
687688
}
688689

689690
var manifestDigest godigest.Digest
690-
for _, manifestDescriptor := range manifestList.Manifests {
691+
for _, manifestDescriptor := range descriptors {
691692
if manifestDescriptor.Platform.Architecture == preferArch && manifestDescriptor.Platform.OS == preferOS {
692693
manifestDigest = manifestDescriptor.Digest
693694
break
@@ -698,7 +699,7 @@ func manifestFromManifestList(
698699
// arch/os, prefer x86/linux before falling back to "first image in the manifestlist"
699700
// as a last resort.
700701
if manifestDigest == "" {
701-
for _, manifestDescriptor := range manifestList.Manifests {
702+
for _, manifestDescriptor := range descriptors {
702703
if manifestDescriptor.Platform.Architecture == "amd64" && manifestDescriptor.Platform.OS == "linux" {
703704
manifestDigest = manifestDescriptor.Digest
704705
break
@@ -707,8 +708,11 @@ func manifestFromManifestList(
707708
}
708709

709710
if manifestDigest == "" {
710-
klog.V(5).Infof("unable to find %s/%s manifest in manifest list %s, doing conservative fail by switching to the first one: %#+v", preferOS, preferArch, ref.String(), manifestList.Manifests[0])
711-
manifestDigest = manifestList.Manifests[0].Digest
711+
klog.V(5).Infof(
712+
"unable to find %s/%s manifest in manifest list %s, doing conservative fail by switching to the first one: %#+v",
713+
preferOS, preferArch, ref.String(), descriptors[0],
714+
)
715+
manifestDigest = descriptors[0].Digest
712716
}
713717

714718
manifest, err := s.Get(ctx, manifestDigest)
@@ -730,33 +734,39 @@ func (imp *ImageStreamImporter) importManifest(
730734
preferArch, preferOS string,
731735
importMode imageapi.ImportModeType,
732736
) (image *imageapi.Image, err error) {
733-
legacyManifestListImport := importMode == "" || importMode == imageapi.ImportModeLegacy
734-
manifestList, isManifestList := manifest.(*manifestlist.DeserializedManifestList)
735-
if isManifestList && legacyManifestListImport {
736-
manifest, d, err = manifestFromManifestList(ctx, manifestList, ref, s, preferArch, preferOS)
737+
738+
// In case we are dealing with an image index or a manifest list, select the right image.
739+
_, isIndex := manifest.(*ocischema.DeserializedImageIndex)
740+
_, isList := manifest.(*manifestlist.DeserializedManifestList)
741+
if isIndex || isList {
742+
if importMode != "" && importMode != imageapi.ImportModeLegacy {
743+
return manifestToImage(manifest, d)
744+
}
745+
746+
manifest, d, err = selectManifestForPlatform(ctx, manifest, ref, s, preferArch, preferOS)
737747
if err != nil {
738748
return nil, formatRepositoryError(ref, err)
739749
}
740750
}
741751

742-
if isManifestList && !legacyManifestListImport {
743-
image, err = manifestListToImage(manifestList, d)
744-
return
745-
} else if deserializedManifest, isSchema2 := manifest.(*schema2.DeserializedManifest); isSchema2 {
746-
imageConfig, getImportConfigErr := b.Get(ctx, deserializedManifest.Config.Digest)
752+
switch manifest := manifest.(type) {
753+
case *schema2.DeserializedManifest:
754+
imageConfig, getImportConfigErr := b.Get(ctx, manifest.Config.Digest)
747755
if getImportConfigErr != nil {
748756
klog.V(5).Infof("unable to get image config by digest %q for image %s: %#v", d, ref.String(), getImportConfigErr)
749757
return image, formatRepositoryError(ref, getImportConfigErr)
750758
}
751-
image, err = schema2OrOCIToImage(deserializedManifest, imageConfig, d)
752-
} else if deserializedManifest, isOCISchema := manifest.(*ocischema.DeserializedManifest); isOCISchema {
753-
imageConfig, getImportConfigErr := b.Get(ctx, deserializedManifest.Config.Digest)
759+
image, err = schema2OrOCIToImage(manifest, imageConfig, d)
760+
761+
case *ocischema.DeserializedManifest:
762+
imageConfig, getImportConfigErr := b.Get(ctx, manifest.Config.Digest)
754763
if getImportConfigErr != nil {
755764
klog.V(5).Infof("unable to get image config by digest %q for image %s: %#v", d, ref.String(), getImportConfigErr)
756765
return image, formatRepositoryError(ref, getImportConfigErr)
757766
}
758767
image, err = schema2OrOCIToImage(manifest, imageConfig, d)
759-
} else {
768+
769+
default:
760770
err = fmt.Errorf("unsupported image manifest type: %T", manifest)
761771
klog.V(5).Info(err)
762772
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package importer
2+
3+
import (
4+
_ "embed"
5+
"github.com/distribution/distribution/v3/manifest/ocischema"
6+
"testing"
7+
)
8+
9+
// imageIndexJSON actually contains the same manifests as manifestListJSON,
10+
// so the same manifests can be used during testing.
11+
//
12+
// sha256:6f9fa17c8be41ca496faf21fcbaba3974f11bc85ecc970fa572a2a18333e681f
13+
//
14+
//go:embed testdata/image-index.json
15+
var imageIndexJSON []byte
16+
17+
func TestImportImageIndex(t *testing.T) {
18+
manifestIndex := &ocischema.DeserializedImageIndex{}
19+
if err := manifestIndex.UnmarshalJSON(imageIndexJSON); err != nil {
20+
t.Fatal(err)
21+
}
22+
23+
testImportManifest(t, manifestIndex, "sha256:6f9fa17c8be41ca496faf21fcbaba3974f11bc85ecc970fa572a2a18333e681f")
24+
}

pkg/image/apiserver/importer/importer_manifestlist_test.go

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,14 @@ func TestImportManifestList(t *testing.T) {
126126
t.Fatal(err)
127127
}
128128

129+
testImportManifest(t, manifestList, "sha256:5020d54ec2de60c4e187128b5a03adda261a7fe78c9c500ffd24ff4af476fb41")
130+
}
131+
132+
func testImportManifest(t *testing.T, manifest distribution.Manifest, manifestDigest godigest.Digest) {
129133
testCases := []struct {
130134
name string
131135
isImport imageapi.ImageStreamImport
132-
manifestList *manifestlist.DeserializedManifestList
136+
root distribution.Manifest
133137
importEntireRepo bool
134138
// the sub manifests that will be imported, as listed in importPlatforms
135139
manifests []struct {
@@ -156,7 +160,7 @@ func TestImportManifestList(t *testing.T) {
156160
},
157161
},
158162
importEntireRepo: true,
159-
manifestList: manifestList,
163+
root: manifest,
160164
manifests: []struct {
161165
raw []byte
162166
digest godigest.Digest
@@ -177,7 +181,7 @@ func TestImportManifestList(t *testing.T) {
177181
},
178182
},
179183
expectedRequests: []godigest.Digest{
180-
// manifest list digest will be empty when importing by tag
184+
// root digest will be empty when importing by tag
181185
"",
182186
// amd64 manifest digest
183187
"sha256:ca013ac5c09f9a9f6db8370c1b759a29fe997d64d6591e9a75b71748858f7da0",
@@ -202,7 +206,7 @@ func TestImportManifestList(t *testing.T) {
202206
},
203207
},
204208
},
205-
manifestList: manifestList,
209+
root: manifest,
206210
manifests: []struct {
207211
raw []byte
208212
digest godigest.Digest
@@ -223,7 +227,7 @@ func TestImportManifestList(t *testing.T) {
223227
},
224228
},
225229
expectedRequests: []godigest.Digest{
226-
// manifest list digest will be empty when importing by tag
230+
// root digest will be empty when importing by tag
227231
"",
228232
// amd64 manifest digest
229233
"sha256:ca013ac5c09f9a9f6db8370c1b759a29fe997d64d6591e9a75b71748858f7da0",
@@ -242,13 +246,13 @@ func TestImportManifestList(t *testing.T) {
242246
},
243247
From: kapi.ObjectReference{
244248
Kind: "DockerImage",
245-
Name: "test@sha256:5020d54ec2de60c4e187128b5a03adda261a7fe78c9c500ffd24ff4af476fb41",
249+
Name: "test@" + manifestDigest.String(),
246250
},
247251
},
248252
},
249253
},
250254
},
251-
manifestList: manifestList,
255+
root: manifest,
252256
manifests: []struct {
253257
raw []byte
254258
digest godigest.Digest
@@ -269,8 +273,8 @@ func TestImportManifestList(t *testing.T) {
269273
},
270274
},
271275
expectedRequests: []godigest.Digest{
272-
// manifest list digest
273-
"sha256:5020d54ec2de60c4e187128b5a03adda261a7fe78c9c500ffd24ff4af476fb41",
276+
// root digest
277+
manifestDigest,
274278
// amd64 manifest digest
275279
"sha256:ca013ac5c09f9a9f6db8370c1b759a29fe997d64d6591e9a75b71748858f7da0",
276280
// arm64 manifest digest
@@ -288,7 +292,7 @@ func TestImportManifestList(t *testing.T) {
288292
},
289293
From: kapi.ObjectReference{
290294
Kind: "DockerImage",
291-
Name: "test@sha256:5020d54ec2de60c4e187128b5a03adda261a7fe78c9c500ffd24ff4af476fb41",
295+
Name: "test@" + manifestDigest.String(),
292296
},
293297
},
294298
{
@@ -297,13 +301,13 @@ func TestImportManifestList(t *testing.T) {
297301
},
298302
From: kapi.ObjectReference{
299303
Kind: "DockerImage",
300-
Name: "test@sha256:5020d54ec2de60c4e187128b5a03adda261a7fe78c9c500ffd24ff4af476fb41",
304+
Name: "test@" + manifestDigest.String(),
301305
},
302306
},
303307
},
304308
},
305309
},
306-
manifestList: manifestList,
310+
root: manifest,
307311
manifests: []struct {
308312
raw []byte
309313
digest godigest.Digest
@@ -324,14 +328,14 @@ func TestImportManifestList(t *testing.T) {
324328
},
325329
},
326330
expectedRequests: []godigest.Digest{
327-
// manifest list digest
328-
"sha256:5020d54ec2de60c4e187128b5a03adda261a7fe78c9c500ffd24ff4af476fb41",
331+
// root digest
332+
manifestDigest,
329333
// amd64 manifest digest
330334
"sha256:ca013ac5c09f9a9f6db8370c1b759a29fe997d64d6591e9a75b71748858f7da0",
331335
// arm64 manifest digest
332336
"sha256:1a06d68cb9117b52965035a5b0fa4c1470ef892e6062ffedb1af1922952e0950",
333-
// manifest list digest
334-
"sha256:5020d54ec2de60c4e187128b5a03adda261a7fe78c9c500ffd24ff4af476fb41",
337+
// root digest
338+
manifestDigest,
335339
// amd64 manifest digest
336340
"sha256:ca013ac5c09f9a9f6db8370c1b759a29fe997d64d6591e9a75b71748858f7da0",
337341
},
@@ -352,7 +356,7 @@ func TestImportManifestList(t *testing.T) {
352356
}
353357

354358
mockRepo := &mockRepository{
355-
manifest: testCase.manifestList,
359+
manifest: testCase.root,
356360
blobs: &mockBlobStore{blobs: configBlobs},
357361
extraManifests: subManifests,
358362
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"manifests": [
3+
{
4+
"digest": "sha256:ca013ac5c09f9a9f6db8370c1b759a29fe997d64d6591e9a75b71748858f7da0",
5+
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
6+
"platform": {
7+
"architecture": "amd64",
8+
"os": "linux"
9+
},
10+
"size": 1152
11+
},
12+
{
13+
"digest": "sha256:1a06d68cb9117b52965035a5b0fa4c1470ef892e6062ffedb1af1922952e0950",
14+
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
15+
"platform": {
16+
"architecture": "arm64",
17+
"os": "linux",
18+
"variant": "v8"
19+
},
20+
"size": 1152
21+
}
22+
],
23+
"mediaType": "application/vnd.oci.image.index.v1+json",
24+
"schemaVersion": 2
25+
}

0 commit comments

Comments
 (0)