diff --git a/pkg/imgpkg/imagedesc/image_ref_descriptors.go b/pkg/imgpkg/imagedesc/image_ref_descriptors.go index 10f6a8925..83eeab22d 100644 --- a/pkg/imgpkg/imagedesc/image_ref_descriptors.go +++ b/pkg/imgpkg/imagedesc/image_ref_descriptors.go @@ -51,9 +51,49 @@ func NewImageRefDescriptorsFromBytes(data []byte) (*ImageRefDescriptors, error) return nil, err } + for _, desc := range descs { + switch { + case desc.Image != nil: + if err := validateImageRefs(*desc.Image); err != nil { + return nil, err + } + case desc.ImageIndex != nil: + if err := validateImageIndexRefs(*desc.ImageIndex); err != nil { + return nil, err + } + } + } + return &ImageRefDescriptors{descs: descs}, nil } +// validateImageRefs checks that a descriptor carries the ref that Ref() reads. +// Every descriptor this package writes has exactly one, but nothing stops a +// hand-written or truncated manifest from arriving without any. +func validateImageRefs(desc ImageDescriptor) error { + if len(desc.Refs) == 0 { + return fmt.Errorf("Expected image descriptor %s to have at least one ref", desc.Manifest.Digest) + } + return nil +} + +func validateImageIndexRefs(desc ImageIndexDescriptor) error { + if len(desc.Refs) == 0 { + return fmt.Errorf("Expected image index descriptor %s to have at least one ref", desc.Digest) + } + for _, img := range desc.Images { + if err := validateImageRefs(img); err != nil { + return err + } + } + for _, idx := range desc.Indexes { + if err := validateImageIndexRefs(idx); err != nil { + return err + } + } + return nil +} + func NewImageRefDescriptors(refs []Metadata, registry Registry) (*ImageRefDescriptors, error) { registry = errRegistry{registry} diff --git a/pkg/imgpkg/imagedesc/image_ref_descriptors_test.go b/pkg/imgpkg/imagedesc/image_ref_descriptors_test.go new file mode 100644 index 000000000..7ca8ed382 --- /dev/null +++ b/pkg/imgpkg/imagedesc/image_ref_descriptors_test.go @@ -0,0 +1,49 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package imagedesc_test + +import ( + "strings" + "testing" + + "carvel.dev/imgpkg/pkg/imgpkg/imagedesc" +) + +func TestNewImageRefDescriptorsFromBytes(t *testing.T) { + t.Run("rejects an image descriptor with no refs", func(t *testing.T) { + manifest := `[{"Image":{"Refs":[],"Manifest":{"Digest":"sha256:aaaa"}}}]` + + _, err := imagedesc.NewImageRefDescriptorsFromBytes([]byte(manifest)) + if err == nil { + t.Fatal("expected an error, got none") + } + if !strings.Contains(err.Error(), "at least one ref") { + t.Errorf("unexpected error: %v", err) + } + }) + + t.Run("rejects an image nested in an index with no refs", func(t *testing.T) { + manifest := `[{"ImageIndex":{"Refs":["registry.example.com/repo@sha256:bbbb"],"Digest":"sha256:bbbb","Images":[{"Refs":[],"Manifest":{"Digest":"sha256:aaaa"}}]}}]` + + _, err := imagedesc.NewImageRefDescriptorsFromBytes([]byte(manifest)) + if err == nil { + t.Fatal("expected an error, got none") + } + if !strings.Contains(err.Error(), "at least one ref") { + t.Errorf("unexpected error: %v", err) + } + }) + + t.Run("accepts a descriptor with a ref", func(t *testing.T) { + manifest := `[{"Image":{"Refs":["registry.example.com/repo@sha256:aaaa"],"Manifest":{"Digest":"sha256:aaaa"}}}]` + + ids, err := imagedesc.NewImageRefDescriptorsFromBytes([]byte(manifest)) + if err != nil { + t.Fatalf("got an error: %v", err) + } + if got := len(ids.Descriptors()); got != 1 { + t.Errorf("expected 1 descriptor, got %d", got) + } + }) +}