@@ -39,6 +39,7 @@ import (
3939 corev1 "k8s.io/api/core/v1"
4040 "k8s.io/apimachinery/pkg/runtime"
4141 "k8s.io/apimachinery/pkg/types"
42+ kerrors "k8s.io/apimachinery/pkg/util/errors"
4243 "k8s.io/apimachinery/pkg/util/sets"
4344 kuberecorder "k8s.io/client-go/tools/record"
4445 "k8s.io/utils/ptr"
@@ -392,7 +393,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
392393
393394 // Get the upstream revision from the artifact digest
394395 // TODO: getRevision resolves the digest, which may change before image is fetched, so it should probaly update ref
395- revision , err := r .getRevision (ref , opts )
396+ revision , ref , desc , err := r .getRevision (ref , opts )
396397 if err != nil {
397398 e := serror .NewGeneric (
398399 fmt .Errorf ("failed to determine artifact digest: %w" , err ),
@@ -454,7 +455,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
454455 return sreconcile .ResultSuccess , nil
455456 }
456457
457- blob , serr := r .fetchArtifact (obj , metadata , ref , opts )
458+ blob , serr := r .fetchArtifact (obj , metadata , ref , desc , opts )
458459 if serr != nil {
459460 conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , serr .Reason , serr .Err .Error ())
460461 return sreconcile .ResultEmpty , serr
@@ -558,63 +559,130 @@ func (r *OCIRepositoryReconciler) selectLayer(obj *ociv1.OCIRepository, image gc
558559 return blob , nil
559560}
560561
561- func (r * OCIRepositoryReconciler ) fetchArtifact (obj * ociv1.OCIRepository , metadata * sourcev1.Artifact , ref name.Reference , options remoteOptions ) (io.ReadCloser , * serror.Generic ) {
562- // Pull artifact from the remote container registry
563- img , err := remote .Image (ref , options ... )
564- if err != nil {
565- e := serror .NewGeneric (
566- fmt .Errorf ("failed to pull artifact from '%s': %w" , obj .Spec .URL , err ),
567- ociv1 .OCIPullFailedReason ,
568- )
569- return nil , e
570- }
562+ func newPullErr (format string , a ... any ) * serror.Generic {
563+ return serror .NewGeneric (fmt .Errorf (format , a ... ), ociv1 .OCIPullFailedReason )
564+ }
571565
572- // Copy the OCI annotations to the internal artifact metadata
573- manifest , err := img .Manifest ()
574- if err != nil {
575- e := serror .NewGeneric (
576- fmt .Errorf ("failed to parse artifact manifest: %w" , err ),
577- ociv1 .OCILayerOperationFailedReason ,
578- )
579- return nil , e
566+ func newLayerOperationErr (format string , a ... any ) * serror.Generic {
567+ return serror .NewGeneric (fmt .Errorf (format , a ... ), ociv1 .OCILayerOperationFailedReason )
568+ }
569+
570+ func (r * OCIRepositoryReconciler ) fetchArtifact (obj * ociv1.OCIRepository , metadata * sourcev1.Artifact , ref name.Reference , desc * remote.Descriptor , options remoteOptions ) (io.ReadCloser , * serror.Generic ) {
571+ switch mt := desc .MediaType ; {
572+ case mt .IsImage ():
573+ // Pull artifact from the remote container registry
574+ img , err := desc .Image ()
575+ if err != nil {
576+ return nil , newPullErr ("failed to parse artifact image from '%s': %w" , obj .Spec .URL , err )
577+ }
578+
579+ // Copy the OCI annotations to the internal artifact metadata
580+ manifest , err := img .Manifest ()
581+ if err != nil {
582+ return nil , newLayerOperationErr ("failed to parse artifact image manifest: %w" , err )
583+ }
584+ metadata .Metadata = manifest .Annotations
585+
586+ // Extract the compressed content from the selected layer
587+ blob , err := r .selectLayer (obj , img )
588+ if err != nil {
589+ e := serror .NewGeneric (err , ociv1 .OCILayerOperationFailedReason )
590+ return nil , e
591+ }
592+ return blob , nil
593+ case mt .IsIndex ():
594+ idx , err := desc .ImageIndex ()
595+ if err != nil {
596+ return nil , newPullErr ("failed to parse artifact index from '%s': %w" , obj .Spec .URL , err )
597+ }
598+
599+ manifest , err := idx .IndexManifest ()
600+ if err != nil {
601+ return nil , newPullErr ("failed to parse artifact index manifest: %w" , err )
602+ }
603+
604+ if len (manifest .Manifests ) == 0 {
605+ return nil , newLayerOperationErr ("empty index" )
606+ }
607+
608+ images := make ([]gcrv1.Image , 0 , len (manifest .Manifests ))
609+
610+ for i := range manifest .Manifests {
611+ manifest := manifest .Manifests [i ]
612+ if manifest .MediaType .IsIndex () {
613+ r .Eventf (obj , corev1 .EventTypeWarning , "OCINestedIndexUnsupported" , "skipping nested index manifest '%s' in '%s'" , manifest .Digest .String (), desc .Digest .String ())
614+ continue
615+ }
616+ if ! manifest .MediaType .IsImage () {
617+ r .Eventf (obj , corev1 .EventTypeNormal , "OCIImageUnsupported" , "skipping runnable image '%s' in '%s'" , manifest .Digest .String (), ref )
618+ continue
619+ }
620+ if manifest .ArtifactType != "" {
621+ img , err := idx .Image (manifest .Digest )
622+ if err != nil {
623+ return nil , newPullErr ("failed to pull artifact image '%s' from '%s': %w" , manifest .Digest .String (), ref , err )
624+ }
625+ images = append (images , img )
626+ }
627+ }
628+
629+ if len (images ) == 0 {
630+ return nil , newPullErr ("no suitable artifacts found in index '%s': %w" , desc .Digest .String (), err )
631+ }
632+
633+ var errs []error
634+ for i := range images {
635+ blob , err := r .selectLayer (obj , images [i ])
636+ if err != nil {
637+ errs = append (errs , err )
638+ continue
639+ }
640+ return blob , nil
641+ }
642+ if len (errs ) > 0 {
643+ return nil , newLayerOperationErr ("%w" , kerrors .NewAggregate (errs ))
644+ }
645+ return nil , newPullErr ("no suitable layers found in index '%s': %w" , desc .Digest .String (), err )
646+ default :
647+ return nil , newLayerOperationErr ("media type '%s' of '%s' is not index or image" , mt , ref )
580648 }
581- metadata . Metadata = manifest . Annotations
649+ }
582650
583- // Extract the compressed content from the selected layer
584- blob , err := r .selectLayer (obj , img )
651+ func (r * OCIRepositoryReconciler ) getDescriptor (ref name.Reference , options remoteOptions ) (* remote.Descriptor , error ) {
652+ // NB: there is no good enought reason to use remote.Head first,
653+ // as it's only in error case that remote.Get won't have to be
654+ // done afterwards anyway
655+ desc , err := remote .Get (ref , options ... )
585656 if err != nil {
586- e := serror .NewGeneric (err , ociv1 .OCILayerOperationFailedReason )
587- return nil , e
657+ return nil , fmt .Errorf ("failed to fetch %w" , err )
588658 }
589- return blob , nil
659+ return desc , nil
660+
590661}
591662
592663// getRevision fetches the upstream digest, returning the revision in the
593664// format '<tag>@<digest>'.
594- func (r * OCIRepositoryReconciler ) getRevision (ref name.Reference , options []remote. Option ) (string , error ) {
665+ func (r * OCIRepositoryReconciler ) getRevision (ref name.Reference , options remoteOptions ) (string , name. Reference , * remote. Descriptor , error ) {
595666 switch ref := ref .(type ) {
596667 case name.Digest :
597668 digest , err := gcrv1 .NewHash (ref .DigestStr ())
598669 if err != nil {
599- return "" , err
670+ return "" , nil , nil , err
600671 }
601- return digest .String (), nil
672+ desc , err := r .getDescriptor (ref , options )
673+ if err != nil {
674+ return "" , nil , nil , fmt .Errorf ("unable to check digest in registry: %w" , err )
675+ }
676+ return digest .String (), ref , desc , nil
602677 case name.Tag :
603- var digest gcrv1.Hash
604-
605- desc , err := remote .Head (ref , options ... )
606- if err == nil {
607- digest = desc .Digest
608- } else {
609- rdesc , err := remote .Get (ref , options ... )
610- if err != nil {
611- return "" , err
612- }
613- digest = rdesc .Descriptor .Digest
678+ desc , err := r .getDescriptor (ref , options )
679+ if err != nil {
680+ return "" , nil , nil , err
614681 }
615- return fmt .Sprintf ("%s@%s" , ref .TagStr (), digest .String ()), nil
682+ digest := desc .Digest .String ()
683+ return fmt .Sprintf ("%s@%s" , ref .TagStr (), digest ), ref .Digest (digest ), desc , nil
616684 default :
617- return "" , fmt .Errorf ("unsupported reference type: %T" , ref )
685+ return "" , nil , nil , fmt .Errorf ("unsupported reference type: %T" , ref )
618686 }
619687}
620688
0 commit comments