@@ -20,6 +20,7 @@ import (
2020 "github.com/docker/docker/api/types"
2121 "github.com/docker/docker/pkg/jsonmessage"
2222 "github.com/google/go-containerregistry/pkg/crane"
23+ oci "github.com/google/go-containerregistry/pkg/v1"
2324 "github.com/google/go-containerregistry/pkg/v1/remote/transport"
2425 "github.com/moby/term"
2526 "github.com/opencontainers/go-digest"
@@ -266,24 +267,27 @@ func (r *Registry) displayEvent(ev remotes.FixupEvent) {
266267}
267268
268269// GetCachedImage returns information about an image from local docker cache.
269- func (r * Registry ) GetCachedImage (ctx context.Context , ref cnab.OCIReference ) (ImageSummary , error ) {
270+ func (r * Registry ) GetCachedImage (ctx context.Context , ref cnab.OCIReference ) (ImageMetadata , error ) {
270271 image := ref .String ()
271272 ctx , log := tracing .StartSpan (ctx , attribute .String ("reference" , image ))
272273 defer log .EndSpan ()
273274
274275 cli , err := docker .GetDockerClient ()
275276 if err != nil {
276- return ImageSummary {}, log .Error (err )
277+ return ImageMetadata {}, log .Error (err )
277278 }
278279
279280 result , _ , err := cli .Client ().ImageInspectWithRaw (ctx , image )
280281 if err != nil {
281- return ImageSummary {}, log .Error (fmt .Errorf ("failed to find image in docker cache: %w" , ErrNotFound {Reference : ref }))
282+ err = fmt .Errorf ("failed to find image in docker cache: %w" , ErrNotFound {Reference : ref })
283+ // log as debug because this isn't a terminal error
284+ log .Debugf (err .Error ())
285+ return ImageMetadata {}, err
282286 }
283287
284- summary , err := NewImageSummary ( image , result )
288+ summary , err := NewImageSummaryFromInspect ( ref , result )
285289 if err != nil {
286- return ImageSummary {}, log .Error (fmt .Errorf ("failed to extract image %s in docker cache: %w" , image , err ))
290+ return ImageMetadata {}, log .Error (fmt .Errorf ("failed to extract image %s in docker cache: %w" , image , err ))
287291 }
288292
289293 return summary , nil
@@ -331,6 +335,39 @@ func (r *Registry) GetBundleMetadata(ctx context.Context, ref cnab.OCIReference,
331335 }, nil
332336}
333337
338+ // GetImageMetadata returns information about an image in a registry
339+ // Use ErrNotFound to detect if the error is because the image is not in the registry.
340+ func (r * Registry ) GetImageMetadata (ctx context.Context , ref cnab.OCIReference , opts RegistryOptions ) (ImageMetadata , error ) {
341+ ctx , span := tracing .StartSpan (ctx , attribute .String ("reference" , ref .String ()))
342+ defer span .EndSpan ()
343+
344+ // Check if we already have the image in the Docker cache
345+ cachedResult , err := r .GetCachedImage (ctx , ref )
346+ if err != nil {
347+ if ! errors .Is (err , ErrNotFound {}) {
348+ return ImageMetadata {}, err
349+ }
350+ }
351+
352+ // Check if we have the repository digest cached for the referenced image
353+ if cachedDigest , err := cachedResult .Digest (); err == nil {
354+ span .SetAttributes (attribute .String ("cached-digest" , cachedDigest .String ()))
355+ return cachedResult , nil
356+ }
357+
358+ // Do a HEAD against the registry to retrieve image metadata without pulling the entire image contents
359+ desc , err := crane .Head (ref .String (), opts .toCraneOptions ()... )
360+ if err != nil {
361+ if notFoundErr := asNotFoundError (err , ref ); notFoundErr != nil {
362+ return ImageMetadata {}, span .Error (notFoundErr )
363+ }
364+ return ImageMetadata {}, span .Errorf ("error fetching image metadata for %s: %w" , ref , err )
365+ }
366+
367+ span .SetAttributes (attribute .String ("fetched-digest" , desc .Digest .String ()))
368+ return NewImageSummaryFromDescriptor (ref , desc )
369+ }
370+
334371// asNotFoundError checks if the error is an HTTP 404 not found error, and if so returns a corresponding ErrNotFound instance.
335372func asNotFoundError (err error , ref cnab.OCIReference ) error {
336373 var httpError * transport.Error
@@ -343,49 +380,56 @@ func asNotFoundError(err error, ref cnab.OCIReference) error {
343380 return nil
344381}
345382
346- // ImageSummary contains information about an OCI image.
347- type ImageSummary struct {
348- types. ImageInspect
349- imageRef cnab. OCIReference
383+ // ImageMetadata contains information about an OCI image.
384+ type ImageMetadata struct {
385+ Reference cnab. OCIReference
386+ RepoDigests [] string
350387}
351388
352- func NewImageSummary (imageRef string , sum types.ImageInspect ) (ImageSummary , error ) {
353- ref , err := cnab .ParseOCIReference (imageRef )
354- if err != nil {
355- return ImageSummary {}, err
356- }
357-
358- img := ImageSummary {
359- imageRef : ref ,
360- ImageInspect : sum ,
389+ func NewImageSummaryFromInspect (ref cnab.OCIReference , sum types.ImageInspect ) (ImageMetadata , error ) {
390+ img := ImageMetadata {
391+ Reference : ref ,
392+ RepoDigests : sum .RepoDigests ,
361393 }
362394 if img .IsZero () {
363- return ImageSummary {}, fmt .Errorf ("invalid image summary for image reference %s" , imageRef )
395+ return ImageMetadata {}, fmt .Errorf ("invalid image summary for image reference %s" , ref )
364396 }
365397
366398 return img , nil
367399}
368400
369- func (i ImageSummary ) GetImageReference () cnab.OCIReference {
370- return i .imageRef
401+ func NewImageSummaryFromDescriptor (ref cnab.OCIReference , desc * oci.Descriptor ) (ImageMetadata , error ) {
402+ repoDigest , err := ref .WithDigest (digest .NewDigestFromHex (desc .Digest .Algorithm , desc .Digest .Hex ))
403+ if err != nil {
404+ return ImageMetadata {}, fmt .Errorf ("error building an OCI reference from image %s and digest %s" , ref .Repository (), ref .Digest ())
405+ }
406+
407+ return ImageMetadata {
408+ Reference : ref ,
409+ RepoDigests : []string {repoDigest .String ()},
410+ }, nil
411+ }
412+
413+ func (i ImageMetadata ) String () string {
414+ return i .Reference .String ()
371415}
372416
373- func (i ImageSummary ) IsZero () bool {
374- return i .ID == ""
417+ func (i ImageMetadata ) IsZero () bool {
418+ return i .String () == ""
375419}
376420
377421// Digest returns the image digest for the image reference.
378- func (i ImageSummary ) Digest () (digest.Digest , error ) {
422+ func (i ImageMetadata ) Digest () (digest.Digest , error ) {
379423 if len (i .RepoDigests ) == 0 {
380- return "" , fmt .Errorf ("failed to get digest for image: %s" , i . imageRef . String () )
424+ return "" , fmt .Errorf ("failed to get digest for image: %s" , i )
381425 }
382426 var imgDigest digest.Digest
383427 for _ , rd := range i .RepoDigests {
384428 imgRef , err := cnab .ParseOCIReference (rd )
385429 if err != nil {
386430 return "" , err
387431 }
388- if imgRef .Repository () != i .imageRef .Repository () {
432+ if imgRef .Repository () != i .Reference .Repository () {
389433 continue
390434 }
391435
@@ -398,7 +442,7 @@ func (i ImageSummary) Digest() (digest.Digest, error) {
398442 }
399443
400444 if imgDigest == "" {
401- return "" , fmt .Errorf ("cannot find image digest for desired repo %s" , i . imageRef . String () )
445+ return "" , fmt .Errorf ("cannot find image digest for desired repo %s" , i )
402446 }
403447
404448 if err := imgDigest .Validate (); err != nil {
0 commit comments