From 7d95fe8db59ab6bb7c18e3179c6e54d2326e8e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Mon, 22 Apr 2024 12:56:36 +0200 Subject: [PATCH] c8d/list: Ignore unexpected image target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don't fail-fast when encountering an image that targets an unexpected descriptor (neither a manifest nor index). Log a warning instead. Signed-off-by: Paweł Gronowski --- daemon/containerd/image_list.go | 7 ++++ daemon/containerd/image_list_test.go | 13 ++++++++ .../testutils/specialimage/configtarget.go | 33 +++++++++++++++++++ internal/testutils/specialimage/multilayer.go | 15 ++++++--- 4 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 internal/testutils/specialimage/configtarget.go diff --git a/daemon/containerd/image_list.go b/daemon/containerd/image_list.go index 80b2409015a3b..641d4e4c0af75 100644 --- a/daemon/containerd/image_list.go +++ b/daemon/containerd/image_list.go @@ -301,6 +301,13 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf return nil }) if err != nil { + if errors.Is(err, errNotManifestOrIndex) { + log.G(ctx).WithFields(log.Fields{ + "error": err, + "image": img.Name, + }).Warn("unexpected image target (neither a manifest nor index)") + return nil, nil, nil + } return nil, nil, err } diff --git a/daemon/containerd/image_list_test.go b/daemon/containerd/image_list_test.go index bd988733fee61..46427b952f8ec 100644 --- a/daemon/containerd/image_list_test.go +++ b/daemon/containerd/image_list_test.go @@ -101,6 +101,9 @@ func TestImageList(t *testing.T) { emptyIndex, err := specialimage.EmptyIndex(blobsDir) assert.NilError(t, err) + configTarget, err := specialimage.ConfigTarget(blobsDir) + assert.NilError(t, err) + cs := &blobsDirContentStore{blobs: filepath.Join(blobsDir, "blobs/sha256")} for _, tc := range []struct { @@ -150,6 +153,16 @@ func TestImageList(t *testing.T) { assert.Check(t, is.Len(all, 2)) }, }, + { + // Make sure an invalid image target doesn't break the whole operation + name: "one good image, second has config as a target", + images: imagesFromIndex(multilayer, configTarget), + check: func(t *testing.T, all []*imagetypes.Summary) { + assert.Check(t, is.Len(all, 1)) + + assert.Check(t, is.Equal(all[0].ID, multilayer.Manifests[0].Digest.String())) + }, + }, } { tc := tc t.Run(tc.name, func(t *testing.T) { diff --git a/internal/testutils/specialimage/configtarget.go b/internal/testutils/specialimage/configtarget.go new file mode 100644 index 0000000000000..944e9d535560f --- /dev/null +++ b/internal/testutils/specialimage/configtarget.go @@ -0,0 +1,33 @@ +package specialimage + +import ( + "github.com/containerd/containerd/platforms" + "github.com/distribution/reference" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +// ConfigTarget creates an image index with an image config being used as an +// image target instead of a manifest or index. +func ConfigTarget(dir string) (*ocispec.Index, error) { + const imageRef = "config:latest" + + ref, err := reference.ParseNormalizedNamed(imageRef) + if err != nil { + return nil, err + } + + desc, err := writeJsonBlob(dir, ocispec.MediaTypeImageConfig, ocispec.Image{ + Platform: platforms.MustParse("linux/amd64"), + Config: ocispec.ImageConfig{ + Env: []string{"FOO=BAR"}, + }, + }) + if err != nil { + return nil, err + } + desc.Annotations = map[string]string{ + "io.containerd.image.name": ref.String(), + } + + return ociImage(dir, ref, desc) +} diff --git a/internal/testutils/specialimage/multilayer.go b/internal/testutils/specialimage/multilayer.go index b7352d019d958..687505196a22b 100644 --- a/internal/testutils/specialimage/multilayer.go +++ b/internal/testutils/specialimage/multilayer.go @@ -102,19 +102,24 @@ func singlePlatformImage(dir string, ref reference.Named, manifest ocispec.Manif } } + if err := writeJson(legacyManifests, filepath.Join(dir, "manifest.json")); err != nil { + return nil, err + } + + return ociImage(dir, ref, manifestDesc) +} + +func ociImage(dir string, ref reference.Named, target ocispec.Descriptor) (*ocispec.Index, error) { idx := ocispec.Index{ Versioned: specs.Versioned{SchemaVersion: 2}, MediaType: ocispec.MediaTypeImageIndex, - Manifests: []ocispec.Descriptor{manifestDesc}, + Manifests: []ocispec.Descriptor{target}, } if err := writeJson(idx, filepath.Join(dir, "index.json")); err != nil { return nil, err } - if err := writeJson(legacyManifests, filepath.Join(dir, "manifest.json")); err != nil { - return nil, err - } - err = os.WriteFile(filepath.Join(dir, "oci-layout"), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0o644) + err := os.WriteFile(filepath.Join(dir, "oci-layout"), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0o644) if err != nil { return nil, err }