@@ -50,6 +50,8 @@ import (
5050 "sigs.k8s.io/controller-runtime/pkg/ratelimiter"
5151
5252 "github.com/fluxcd/pkg/apis/meta"
53+ "github.com/fluxcd/pkg/oci"
54+ "github.com/fluxcd/pkg/oci/auth/login"
5355 "github.com/fluxcd/pkg/runtime/conditions"
5456 helper "github.com/fluxcd/pkg/runtime/controller"
5557 "github.com/fluxcd/pkg/runtime/events"
@@ -64,14 +66,6 @@ import (
6466 "github.com/fluxcd/source-controller/internal/util"
6567)
6668
67- const (
68- ClientCert = "certFile"
69- ClientKey = "keyFile"
70- CACert = "caFile"
71- OCISourceKey = "org.opencontainers.image.source"
72- OCIRevisionKey = "org.opencontainers.image.revision"
73- )
74-
7569// ociRepositoryReadyCondition contains the information required to summarize a
7670// v1beta2.OCIRepository Ready Condition.
7771var ociRepositoryReadyCondition = summarize.Conditions {
@@ -297,7 +291,9 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
297291 ctxTimeout , cancel := context .WithTimeout (ctx , obj .Spec .Timeout .Duration )
298292 defer cancel ()
299293
300- // Generate the registry credential keychain
294+ options := r .craneOptions (ctxTimeout )
295+
296+ // Generate the registry credential keychain either from static credentials or using cloud OIDC
301297 keychain , err := r .keychain (ctx , obj )
302298 if err != nil {
303299 e := serror .NewGeneric (
@@ -307,6 +303,22 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
307303 conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , e .Reason , e .Err .Error ())
308304 return sreconcile .ResultEmpty , e
309305 }
306+ options = append (options , crane .WithAuthFromKeychain (keychain ))
307+
308+ if obj .Spec .Provider != sourcev1 .GenericOCIProvider {
309+ auth , authErr := r .oidcAuth (ctxTimeout , obj )
310+ if authErr != nil && ! errors .Is (authErr , oci .ErrUnconfiguredProvider ) {
311+ e := serror .NewGeneric (
312+ fmt .Errorf ("failed to get credential from %s: %w" , obj .Spec .Provider , authErr ),
313+ sourcev1 .AuthenticationFailedReason ,
314+ )
315+ conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , e .Reason , e .Err .Error ())
316+ return sreconcile .ResultEmpty , e
317+ }
318+ if auth != nil {
319+ options = append (options , crane .WithAuth (auth ))
320+ }
321+ }
310322
311323 // Generate the transport for remote operations
312324 transport , err := r .transport (ctx , obj )
@@ -318,9 +330,12 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
318330 conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , e .Reason , e .Err .Error ())
319331 return sreconcile .ResultEmpty , e
320332 }
333+ if transport != nil {
334+ options = append (options , crane .WithTransport (transport ))
335+ }
321336
322337 // Determine which artifact revision to pull
323- url , err := r .getArtifactURL (ctxTimeout , obj , keychain , transport )
338+ url , err := r .getArtifactURL (obj , options )
324339 if err != nil {
325340 e := serror .NewGeneric (
326341 fmt .Errorf ("failed to determine the artifact address for '%s': %w" , obj .Spec .URL , err ),
@@ -330,7 +345,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
330345 }
331346
332347 // Pull artifact from the remote container registry
333- img , err := crane .Pull (url , r . craneOptions ( ctxTimeout , keychain , transport ) ... )
348+ img , err := crane .Pull (url , options ... )
334349 if err != nil {
335350 e := serror .NewGeneric (
336351 fmt .Errorf ("failed to pull artifact from '%s': %w" , obj .Spec .URL , err ),
@@ -437,12 +452,16 @@ func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository
437452 return "" , err
438453 }
439454
455+ imageName := strings .TrimPrefix (url , ref .Context ().RegistryStr ())
456+ if s := strings .Split (imageName , ":" ); len (s ) > 1 {
457+ return "" , fmt .Errorf ("URL must not contain a tag; remove ':%s'" , s [1 ])
458+ }
459+
440460 return ref .Context ().Name (), nil
441461}
442462
443463// getArtifactURL determines which tag or digest should be used and returns the OCI artifact FQN.
444- func (r * OCIRepositoryReconciler ) getArtifactURL (ctx context.Context ,
445- obj * sourcev1.OCIRepository , keychain authn.Keychain , transport http.RoundTripper ) (string , error ) {
464+ func (r * OCIRepositoryReconciler ) getArtifactURL (obj * sourcev1.OCIRepository , options []crane.Option ) (string , error ) {
446465 url , err := r .parseRepositoryURL (obj )
447466 if err != nil {
448467 return "" , err
@@ -454,7 +473,7 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context,
454473 }
455474
456475 if obj .Spec .Reference .SemVer != "" {
457- tag , err := r .getTagBySemver (ctx , url , obj .Spec .Reference .SemVer , keychain , transport )
476+ tag , err := r .getTagBySemver (url , obj .Spec .Reference .SemVer , options )
458477 if err != nil {
459478 return "" , err
460479 }
@@ -471,9 +490,8 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context,
471490
472491// getTagBySemver call the remote container registry, fetches all the tags from the repository,
473492// and returns the latest tag according to the semver expression.
474- func (r * OCIRepositoryReconciler ) getTagBySemver (ctx context.Context ,
475- url , exp string , keychain authn.Keychain , transport http.RoundTripper ) (string , error ) {
476- tags , err := crane .ListTags (url , r .craneOptions (ctx , keychain , transport )... )
493+ func (r * OCIRepositoryReconciler ) getTagBySemver (url , exp string , options []crane.Option ) (string , error ) {
494+ tags , err := crane .ListTags (url , options ... )
477495 if err != nil {
478496 return "" , err
479497 }
@@ -567,20 +585,20 @@ func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *sourcev1.O
567585 transport := remote .DefaultTransport .Clone ()
568586 tlsConfig := transport .TLSClientConfig
569587
570- if clientCert , ok := certSecret .Data [ClientCert ]; ok {
588+ if clientCert , ok := certSecret .Data [oci . ClientCert ]; ok {
571589 // parse and set client cert and secret
572- if clientKey , ok := certSecret .Data [ClientKey ]; ok {
590+ if clientKey , ok := certSecret .Data [oci . ClientKey ]; ok {
573591 cert , err := tls .X509KeyPair (clientCert , clientKey )
574592 if err != nil {
575593 return nil , err
576594 }
577595 tlsConfig .Certificates = append (tlsConfig .Certificates , cert )
578596 } else {
579- return nil , fmt .Errorf ("'%s' found in secret, but no %s" , ClientCert , ClientKey )
597+ return nil , fmt .Errorf ("'%s' found in secret, but no %s" , oci . ClientCert , oci . ClientKey )
580598 }
581599 }
582600
583- if caCert , ok := certSecret .Data [CACert ]; ok {
601+ if caCert , ok := certSecret .Data [oci . CACert ]; ok {
584602 syscerts , err := x509 .SystemCertPool ()
585603 if err != nil {
586604 return nil , err
@@ -592,20 +610,34 @@ func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *sourcev1.O
592610
593611}
594612
613+ // oidcAuth generates the OIDC credential authenticator based on the specified cloud provider.
614+ func (r * OCIRepositoryReconciler ) oidcAuth (ctx context.Context , obj * sourcev1.OCIRepository ) (authn.Authenticator , error ) {
615+ url := strings .TrimPrefix (obj .Spec .URL , sourcev1 .OCIRepositoryPrefix )
616+ ref , err := name .ParseReference (url )
617+ if err != nil {
618+ return nil , fmt .Errorf ("failed to parse URL '%s': %w" , obj .Spec .URL , err )
619+ }
620+
621+ opts := login.ProviderOptions {}
622+ switch obj .Spec .Provider {
623+ case sourcev1 .AmazonOCIProvider :
624+ opts .AwsAutoLogin = true
625+ case sourcev1 .AzureOCIProvider :
626+ opts .AzureAutoLogin = true
627+ case sourcev1 .GoogleOCIProvider :
628+ opts .GcpAutoLogin = true
629+ }
630+
631+ return login .NewManager ().Login (ctx , url , ref , opts )
632+ }
633+
595634// craneOptions sets the auth headers, timeout and user agent
596635// for all operations against remote container registries.
597- func (r * OCIRepositoryReconciler ) craneOptions (ctx context.Context ,
598- keychain authn.Keychain , transport http.RoundTripper ) []crane.Option {
636+ func (r * OCIRepositoryReconciler ) craneOptions (ctx context.Context ) []crane.Option {
599637 options := []crane.Option {
600638 crane .WithContext (ctx ),
601- crane .WithUserAgent ("flux/v2" ),
602- crane .WithAuthFromKeychain (keychain ),
639+ crane .WithUserAgent (oci .UserAgent ),
603640 }
604-
605- if transport != nil {
606- options = append (options , crane .WithTransport (transport ))
607- }
608-
609641 return options
610642}
611643
@@ -834,10 +866,10 @@ func (r *OCIRepositoryReconciler) notify(ctx context.Context,
834866 // enrich message with upstream annotations if found
835867 if info := newObj .GetArtifact ().Metadata ; info != nil {
836868 var source , revision string
837- if val , ok := info [OCISourceKey ]; ok {
869+ if val , ok := info [oci . SourceAnnotation ]; ok {
838870 source = val
839871 }
840- if val , ok := info [OCIRevisionKey ]; ok {
872+ if val , ok := info [oci . RevisionAnnotation ]; ok {
841873 revision = val
842874 }
843875 if source != "" && revision != "" {
0 commit comments