diff --git a/cmd/oras/internal/display/status/utils.go b/cmd/oras/internal/display/status/utils.go index b5ec5a054..40e857c42 100644 --- a/cmd/oras/internal/display/status/utils.go +++ b/cmd/oras/internal/display/status/utils.go @@ -15,14 +15,6 @@ limitations under the License. package status -import ocispec "github.com/opencontainers/image-spec/specs-go/v1" - -// GenerateContentKey generates a unique key for each content descriptor, using -// its digest and name if applicable. -func GenerateContentKey(desc ocispec.Descriptor) string { - return desc.Digest.String() + desc.Annotations[ocispec.AnnotationTitle] -} - // Prompts for pull events. const ( PullPromptDownloading = "Downloading" diff --git a/cmd/oras/root/pull.go b/cmd/oras/root/pull.go index 7cc1e0874..1cf4cda60 100644 --- a/cmd/oras/root/pull.go +++ b/cmd/oras/root/pull.go @@ -35,6 +35,7 @@ import ( oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/fileref" "oras.land/oras/cmd/oras/internal/option" + "oras.land/oras/internal/descriptor" "oras.land/oras/internal/graph" ) @@ -172,7 +173,7 @@ func doPull(ctx context.Context, src oras.ReadOnlyTarget, dst oras.GraphTarget, var getConfigOnce sync.Once opts.FindSuccessors = func(ctx context.Context, fetcher content.Fetcher, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { statusFetcher := content.FetcherFunc(func(ctx context.Context, target ocispec.Descriptor) (fetched io.ReadCloser, fetchErr error) { - if _, ok := printed.LoadOrStore(status.GenerateContentKey(target), true); ok { + if _, ok := printed.LoadOrStore(descriptor.GenerateContentKey(target), true); ok { return fetcher.Fetch(ctx, target) } if err := statusHandler.OnNodeDownloading(target); err != nil { @@ -260,7 +261,7 @@ func doPull(ctx context.Context, src oras.ReadOnlyTarget, dst oras.GraphTarget, } } } - printed.Store(status.GenerateContentKey(desc), true) + printed.Store(descriptor.GenerateContentKey(desc), true) return statusHandler.OnNodeDownloaded(desc) } @@ -270,7 +271,7 @@ func doPull(ctx context.Context, src oras.ReadOnlyTarget, dst oras.GraphTarget, } func notifyOnce(notified *sync.Map, s ocispec.Descriptor, notify func(ocispec.Descriptor) error) error { - if _, loaded := notified.LoadOrStore(status.GenerateContentKey(s), true); !loaded { + if _, loaded := notified.LoadOrStore(descriptor.GenerateContentKey(s), true); !loaded { return notify(s) } return nil diff --git a/internal/descriptor/descriptor.go b/internal/descriptor/descriptor.go index ba0123a27..46ad93249 100644 --- a/internal/descriptor/descriptor.go +++ b/internal/descriptor/descriptor.go @@ -46,3 +46,9 @@ func GetTitleOrMediaType(desc ocispec.Descriptor) (name string, isTitle bool) { } return name, true } + +// GenerateContentKey generates a unique key for each content descriptor using +// digest and name. +func GenerateContentKey(desc ocispec.Descriptor) string { + return desc.Digest.String() + desc.Annotations[ocispec.AnnotationTitle] +} diff --git a/internal/descriptor/descriptor_test.go b/internal/descriptor/descriptor_test.go index 82b54c2b8..0cad86432 100644 --- a/internal/descriptor/descriptor_test.go +++ b/internal/descriptor/descriptor_test.go @@ -23,26 +23,71 @@ import ( "oras.land/oras/internal/descriptor" ) -func TestIsImageManifest(t *testing.T) { - imageDesc := ocispec.Descriptor{ +var ( + artifactDesc = ocispec.Descriptor{ + MediaType: "application/vnd.cncf.oras.artifact.manifest.v1+json", + Digest: "sha256:772fbebcda7e6937de01295bae28360afd463c2d5f1f7aca59a3ef267608bc66", + Size: 568, + } + + imageDesc = ocispec.Descriptor{ MediaType: "application/vnd.oci.image.manifest.v1+json", Digest: "sha256:2e0e0fe1fb3edbcdddad941c90d2b51e25a6bcd593e82545441a216de7bfa834", Size: 474, } + titledDesc = ocispec.Descriptor{ + MediaType: "application/vnd.oci.image.manifest.v1+json", + Digest: "sha256:2e0e0fe1fb3edbcdddad941c90d2b51e25a6bcd593e82545441a216de7bfa834", + Size: 474, + Annotations: map[string]string{"org.opencontainers.image.title": "shaboozey"}, + } +) + +func TestDescriptor_IsImageManifest(t *testing.T) { got := descriptor.IsImageManifest(imageDesc) if !reflect.DeepEqual(got, true) { t.Fatalf("IsImageManifest() got %v, want %v", got, true) } - artifactDesc := ocispec.Descriptor{ - MediaType: "application/vnd.cncf.oras.artifact.manifest.v1+json", - Digest: "sha256:772fbebcda7e6937de01295bae28360afd463c2d5f1f7aca59a3ef267608bc66", - Size: 568, - } - got = descriptor.IsImageManifest(artifactDesc) if !reflect.DeepEqual(got, false) { t.Fatalf("IsImageManifest() got %v, want %v", got, false) } } + +func TestDescriptor_ShortDigest(t *testing.T) { + expected := "2e0e0fe1fb3e" + got := descriptor.ShortDigest(titledDesc) + if expected != got { + t.Fatalf("GetTitleOrMediaType() got %v, want %v", got, expected) + } +} + +func TestDescriptor_GetTitleOrMediaType(t *testing.T) { + expected := "application/vnd.oci.image.manifest.v1+json" + name, isTitle := descriptor.GetTitleOrMediaType(imageDesc) + if expected != name { + t.Fatalf("GetTitleOrMediaType() got %v, want %v", name, expected) + } + if false != isTitle { + t.Fatalf("GetTitleOrMediaType() got %v, want %v", isTitle, false) + } + + expected = "shaboozey" + name, isTitle = descriptor.GetTitleOrMediaType(titledDesc) + if expected != name { + t.Fatalf("GetTitleOrMediaType() got %v, want %v", name, expected) + } + if true != isTitle { + t.Fatalf("GetTitleOrMediaType() got %v, want %v", isTitle, false) + } +} + +func TestDescriptor_GenerateContentKey(t *testing.T) { + expected := "sha256:2e0e0fe1fb3edbcdddad941c90d2b51e25a6bcd593e82545441a216de7bfa834shaboozey" + got := descriptor.GenerateContentKey(titledDesc) + if expected != got { + t.Fatalf("GetTitleOrMediaType() got %v, want %v", got, expected) + } +}