@@ -50,6 +50,8 @@ import (
50
50
"sigs.k8s.io/controller-runtime/pkg/ratelimiter"
51
51
52
52
"github.com/fluxcd/pkg/apis/meta"
53
+ "github.com/fluxcd/pkg/oci"
54
+ "github.com/fluxcd/pkg/oci/auth/login"
53
55
"github.com/fluxcd/pkg/runtime/conditions"
54
56
helper "github.com/fluxcd/pkg/runtime/controller"
55
57
"github.com/fluxcd/pkg/runtime/events"
@@ -64,14 +66,6 @@ import (
64
66
"github.com/fluxcd/source-controller/internal/util"
65
67
)
66
68
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
-
75
69
// ociRepositoryReadyCondition contains the information required to summarize a
76
70
// v1beta2.OCIRepository Ready Condition.
77
71
var ociRepositoryReadyCondition = summarize.Conditions {
@@ -297,7 +291,9 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
297
291
ctxTimeout , cancel := context .WithTimeout (ctx , obj .Spec .Timeout .Duration )
298
292
defer cancel ()
299
293
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
301
297
keychain , err := r .keychain (ctx , obj )
302
298
if err != nil {
303
299
e := serror .NewGeneric (
@@ -307,6 +303,22 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
307
303
conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , e .Reason , e .Err .Error ())
308
304
return sreconcile .ResultEmpty , e
309
305
}
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
+ }
310
322
311
323
// Generate the transport for remote operations
312
324
transport , err := r .transport (ctx , obj )
@@ -318,9 +330,12 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
318
330
conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , e .Reason , e .Err .Error ())
319
331
return sreconcile .ResultEmpty , e
320
332
}
333
+ if transport != nil {
334
+ options = append (options , crane .WithTransport (transport ))
335
+ }
321
336
322
337
// Determine which artifact revision to pull
323
- url , err := r .getArtifactURL (ctxTimeout , obj , keychain , transport )
338
+ url , err := r .getArtifactURL (obj , options )
324
339
if err != nil {
325
340
e := serror .NewGeneric (
326
341
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
330
345
}
331
346
332
347
// 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 ... )
334
349
if err != nil {
335
350
e := serror .NewGeneric (
336
351
fmt .Errorf ("failed to pull artifact from '%s': %w" , obj .Spec .URL , err ),
@@ -437,12 +452,16 @@ func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository
437
452
return "" , err
438
453
}
439
454
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
+
440
460
return ref .Context ().Name (), nil
441
461
}
442
462
443
463
// 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 ) {
446
465
url , err := r .parseRepositoryURL (obj )
447
466
if err != nil {
448
467
return "" , err
@@ -454,7 +473,7 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context,
454
473
}
455
474
456
475
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 )
458
477
if err != nil {
459
478
return "" , err
460
479
}
@@ -471,9 +490,8 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context,
471
490
472
491
// getTagBySemver call the remote container registry, fetches all the tags from the repository,
473
492
// 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 ... )
477
495
if err != nil {
478
496
return "" , err
479
497
}
@@ -567,20 +585,20 @@ func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *sourcev1.O
567
585
transport := remote .DefaultTransport .Clone ()
568
586
tlsConfig := transport .TLSClientConfig
569
587
570
- if clientCert , ok := certSecret .Data [ClientCert ]; ok {
588
+ if clientCert , ok := certSecret .Data [oci . ClientCert ]; ok {
571
589
// parse and set client cert and secret
572
- if clientKey , ok := certSecret .Data [ClientKey ]; ok {
590
+ if clientKey , ok := certSecret .Data [oci . ClientKey ]; ok {
573
591
cert , err := tls .X509KeyPair (clientCert , clientKey )
574
592
if err != nil {
575
593
return nil , err
576
594
}
577
595
tlsConfig .Certificates = append (tlsConfig .Certificates , cert )
578
596
} 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 )
580
598
}
581
599
}
582
600
583
- if caCert , ok := certSecret .Data [CACert ]; ok {
601
+ if caCert , ok := certSecret .Data [oci . CACert ]; ok {
584
602
syscerts , err := x509 .SystemCertPool ()
585
603
if err != nil {
586
604
return nil , err
@@ -592,20 +610,34 @@ func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *sourcev1.O
592
610
593
611
}
594
612
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
+
595
634
// craneOptions sets the auth headers, timeout and user agent
596
635
// 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 {
599
637
options := []crane.Option {
600
638
crane .WithContext (ctx ),
601
- crane .WithUserAgent ("flux/v2" ),
602
- crane .WithAuthFromKeychain (keychain ),
639
+ crane .WithUserAgent (oci .UserAgent ),
603
640
}
604
-
605
- if transport != nil {
606
- options = append (options , crane .WithTransport (transport ))
607
- }
608
-
609
641
return options
610
642
}
611
643
@@ -834,10 +866,10 @@ func (r *OCIRepositoryReconciler) notify(ctx context.Context,
834
866
// enrich message with upstream annotations if found
835
867
if info := newObj .GetArtifact ().Metadata ; info != nil {
836
868
var source , revision string
837
- if val , ok := info [OCISourceKey ]; ok {
869
+ if val , ok := info [oci . SourceAnnotation ]; ok {
838
870
source = val
839
871
}
840
- if val , ok := info [OCIRevisionKey ]; ok {
872
+ if val , ok := info [oci . RevisionAnnotation ]; ok {
841
873
revision = val
842
874
}
843
875
if source != "" && revision != "" {
0 commit comments