@@ -26,13 +26,15 @@ import (
2626
2727 "github.com/stretchr/testify/require"
2828 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
29+ apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
2930 "k8s.io/apimachinery/pkg/api/errors"
3031 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3132 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3233 "k8s.io/apimachinery/pkg/runtime/schema"
3334 "k8s.io/apimachinery/pkg/util/wait"
3435 "k8s.io/cli-runtime/pkg/genericclioptions"
3536 "k8s.io/client-go/dynamic"
37+ "k8s.io/client-go/rest"
3638 "k8s.io/client-go/util/retry"
3739 "sigs.k8s.io/yaml"
3840
@@ -47,21 +49,25 @@ import (
4749func TestClusterScoped (t * testing.T ) {
4850 t .Parallel ()
4951 // name & test type defined by letters - cc - cluster-cluster so its easier to identify failures in the logs.
50- testHappyCase (t , "cc" , apiextensionsv1 .ClusterScoped , kubebindv1alpha2 .ClusterScope )
52+ testHappyCase (t , "cc-prefixed" , apiextensionsv1 .ClusterScoped , apiextensionsv1 .ClusterScoped , kubebindv1alpha2 .ClusterScope , kubebindv1alpha2 .IsolationPrefixed )
53+ testHappyCase (t , "cc-none" , apiextensionsv1 .ClusterScoped , apiextensionsv1 .ClusterScoped , kubebindv1alpha2 .ClusterScope , kubebindv1alpha2 .IsolationNone )
54+ testHappyCase (t , "cc-namespaced" , apiextensionsv1 .ClusterScoped , apiextensionsv1 .NamespaceScoped , kubebindv1alpha2 .ClusterScope , kubebindv1alpha2 .IsolationNamespaced )
5155}
5256
5357func TestNamespacedScoped (t * testing.T ) {
5458 t .Parallel ()
5559
56- testHappyCase (t , "nn" , apiextensionsv1 .NamespaceScoped , kubebindv1alpha2 .NamespacedScope )
57- testHappyCase (t , "nc" , apiextensionsv1 .NamespaceScoped , kubebindv1alpha2 .ClusterScope )
60+ testHappyCase (t , "nn" , apiextensionsv1 .NamespaceScoped , apiextensionsv1 . NamespaceScoped , kubebindv1alpha2 .NamespacedScope , "" )
61+ testHappyCase (t , "nc" , apiextensionsv1 .NamespaceScoped , apiextensionsv1 . NamespaceScoped , kubebindv1alpha2 .ClusterScope , "" )
5862}
5963
6064func testHappyCase (
6165 t * testing.T ,
6266 name string ,
63- resourceScope apiextensionsv1.ResourceScope ,
67+ consumerResourceScope apiextensionsv1.ResourceScope ,
68+ providerResourceScope apiextensionsv1.ResourceScope ,
6469 informerScope kubebindv1alpha2.InformerScope ,
70+ isolationStrategy kubebindv1alpha2.Isolation ,
6571) {
6672 ctx , cancel := context .WithCancel (context .Background ())
6773 t .Cleanup (cancel ) // Commented out to prevent cleanup of kcp assets
@@ -72,24 +78,36 @@ func testHappyCase(
7278 providerConfig , providerKubeconfig := framework .NewWorkspace (t , framework .ClientConfig (t ), framework .WithName ("%s-provider-%s" , name , suffix ))
7379
7480 t .Logf ("Installing kubebind CRDs" )
75- framework .InstallKubebindCRDs (t , providerConfig )
81+ framework .InstallKubeBindCRDs (t , providerConfig )
7682
7783 t .Logf ("Starting backend with random port" )
78- addr , _ := framework .StartBackend (t , "--kubeconfig=" + providerKubeconfig , "--listen-address=:0" , "--consumer-scope=" + string (informerScope ))
84+ addr , _ := framework .StartBackend (t ,
85+ "--kubeconfig=" + providerKubeconfig ,
86+ "--listen-address=:0" ,
87+ "--consumer-scope=" + string (informerScope ),
88+ "--cluster-scoped-isolation=" + string (isolationStrategy ),
89+ )
7990
8091 t .Logf ("Creating CRD on provider side" )
8192 examples .Bootstrap (t , framework .DiscoveryClient (t , providerConfig ), framework .DynamicClient (t , providerConfig ), nil )
8293
94+ // For namespaced-isolated cluster-scoped objects, the CRD needs to be namespaced on the provider side,
95+ // but cluster-scoped on the consumer side. To make this setup possible without introducing another CRD,
96+ // we will simply "hack" the CRD here on the providerside to make it work.
97+ if providerResourceScope == apiextensionsv1 .NamespaceScoped && consumerResourceScope == apiextensionsv1 .ClusterScoped {
98+ t .Logf ("Changing sheriff CRD scope to namespaced on the provider side" )
99+ toggleCRDScope (t , ctx , providerConfig , "sheriffs.wildwest.dev" , apiextensionsv1 .NamespaceScoped )
100+ }
101+
83102 t .Logf ("Creating consumer workspace and starting konnector" )
84103 consumerConfig , consumerKubeconfig := framework .NewWorkspace (t , framework .ClientConfig (t ), framework .WithName ("%s-consumer-%s" , name , suffix ))
85104 framework .StartKonnector (t , consumerConfig , "--kubeconfig=" + consumerKubeconfig )
86105
87106 serviceGVR := schema.GroupVersionResource {Group : "wildwest.dev" , Version : "v1alpha1" , Resource : "cowboys" }
88- if resourceScope == apiextensionsv1 .ClusterScoped {
89- serviceGVR = schema.GroupVersionResource {Group : "wildwest.dev" , Version : "v1alpha1" , Resource : "sheriffs" }
90- }
91107 templateRef := "cowboys"
92- if resourceScope == apiextensionsv1 .ClusterScoped {
108+
109+ if consumerResourceScope == apiextensionsv1 .ClusterScoped {
110+ serviceGVR = schema.GroupVersionResource {Group : "wildwest.dev" , Version : "v1alpha1" , Resource : "sheriffs" }
93111 templateRef = "sheriffs"
94112 }
95113
@@ -100,8 +118,14 @@ func testHappyCase(
100118 providerBindClient := framework .BindClient (t , providerConfig )
101119
102120 // Instance variables removed - now seeded directly in test
121+ // These two namespaces are where the "main" object resides on each cluster.
103122 consumerNs , providerNs := "wild-west" , "unknown"
104123
124+ // When namespaced isolation is used (i.e. cluster-scoped objects on the consumer
125+ // turn into namespaced objects on the provider cluster), permission claimed objects
126+ // are still synced into their respective APIServiceNamespace-managed namespaces.
127+ permClaimNs := "unknown"
128+
105129 // cluster namespace is the main "contract" namespace, i.e. where the BoundSchema and other
106130 // bind-related objects reside.
107131 clusterNs , clusterScopedUpInsName := "unknown" , "unknown"
@@ -113,7 +137,7 @@ func testHappyCase(
113137 // For sheriffs: sheriff-badge-credentials (referenced), sheriff-jurisdiction-config (label selector)
114138 var referencedSecretName , labelSelectedSecretName string
115139 var filename string
116- if resourceScope == apiextensionsv1 .NamespaceScoped {
140+ if consumerResourceScope == apiextensionsv1 .NamespaceScoped {
117141 referencedSecretName = "colt-45-permit" //nolint:gosec
118142 labelSelectedSecretName = "cowboy-gang-affiliation"
119143 filename = "cr-cowboy.yaml"
@@ -368,7 +392,7 @@ func testHappyCase(
368392 // We need to establish namespace only in cluster scope for cluster scoped resources.
369393 // Else we can trust sync object namespace as it will be the same.
370394 if informerScope == kubebindv1alpha2 .ClusterScope &&
371- resourceScope == apiextensionsv1 .ClusterScoped {
395+ consumerResourceScope == apiextensionsv1 .ClusterScoped {
372396 if providerNs == "unknown" {
373397 t .Fatal ("providerNS is not set. Programming error in the test." )
374398 }
@@ -383,14 +407,18 @@ func testHappyCase(
383407
384408 for _ , namespace := range namespaces .Items {
385409 if strings .Contains (namespace .Name , consumerNs ) && namespace .Status .Namespace != "" {
386- providerNs = namespace .Status .Namespace
410+ permClaimNs = namespace .Status .Namespace
387411 return true
388412 }
389413 }
390414
391415 return false
392416 }, wait .ForeverTestTimeout , time .Millisecond * 100 , "waiting for APIServiceNamespace to be created on provider side" )
393- require .NotEmpty (t , providerNs , "No cluster namespaces found" )
417+ require .NotEmpty (t , permClaimNs , "No permission claim namespaces found" )
418+
419+ t .Logf ("permclaim namespace detected as: %q" , permClaimNs )
420+ } else {
421+ permClaimNs = providerNs
394422 }
395423 },
396424 },
@@ -399,13 +427,13 @@ func testHappyCase(
399427 step : func (t * testing.T ) {
400428 t .Logf ("Waiting for referenced secret to be synced to provider side" )
401429 require .Eventually (t , func () bool {
402- _ , err := providerCoreClient .Secrets (providerNs ).Get (ctx , referencedSecretName , metav1.GetOptions {})
430+ _ , err := providerCoreClient .Secrets (permClaimNs ).Get (ctx , referencedSecretName , metav1.GetOptions {})
403431 return err == nil
404432 }, time .Minute * 2 , time .Millisecond * 100 , "waiting for referenced secret to be synced to provider side" )
405433
406434 t .Logf ("Waiting for label-selected secret to be synced to provider side" )
407435 require .Eventually (t , func () bool {
408- _ , err := providerCoreClient .Secrets (providerNs ).Get (ctx , labelSelectedSecretName , metav1.GetOptions {})
436+ _ , err := providerCoreClient .Secrets (permClaimNs ).Get (ctx , labelSelectedSecretName , metav1.GetOptions {})
409437 return err == nil
410438 }, wait .ForeverTestTimeout , time .Millisecond * 100 , "waiting for label-selected secret to be synced to provider side" )
411439
@@ -424,7 +452,7 @@ func testHappyCase(
424452 name : "instance deleted upstream is recreated" ,
425453 step : func (t * testing.T ) {
426454 var err error
427- if resourceScope == apiextensionsv1 .NamespaceScoped {
455+ if providerResourceScope == apiextensionsv1 .NamespaceScoped {
428456 err = providerClient .Namespace (providerNs ).Delete (ctx , "test" , metav1.DeleteOptions {})
429457 } else {
430458 err = providerClient .Delete (ctx , clusterScopedUpInsName , metav1.DeleteOptions {})
@@ -433,7 +461,7 @@ func testHappyCase(
433461
434462 require .Eventually (t , func () bool {
435463 var err error
436- if resourceScope == apiextensionsv1 .NamespaceScoped {
464+ if providerResourceScope == apiextensionsv1 .NamespaceScoped {
437465 _ , err = providerClient .Namespace (providerNs ).Get (ctx , "test" , metav1.GetOptions {})
438466 } else {
439467 _ , err = providerClient .Get (ctx , clusterScopedUpInsName , metav1.GetOptions {})
@@ -448,13 +476,13 @@ func testHappyCase(
448476 err := retry .RetryOnConflict (retry .DefaultRetry , func () error {
449477 var obj * unstructured.Unstructured
450478 var err error
451- if resourceScope == apiextensionsv1 .NamespaceScoped {
479+ if consumerResourceScope == apiextensionsv1 .NamespaceScoped {
452480 obj , err = consumerClient .Namespace (consumerNs ).Get (ctx , "test" , metav1.GetOptions {})
453481 } else {
454482 obj , err = consumerClient .Get (ctx , "test" , metav1.GetOptions {})
455483 }
456484 require .NoError (t , err )
457- if resourceScope == apiextensionsv1 .NamespaceScoped {
485+ if consumerResourceScope == apiextensionsv1 .NamespaceScoped {
458486 unstructured .SetNestedField (obj .Object , "Updated cowboy intent" , "spec" , "intent" ) //nolint:errcheck
459487 _ , err = consumerClient .Namespace (consumerNs ).Update (ctx , obj , metav1.UpdateOptions {})
460488 } else {
@@ -468,7 +496,7 @@ func testHappyCase(
468496 require .Eventually (t , func () bool {
469497 var obj * unstructured.Unstructured
470498 var err error
471- if resourceScope == apiextensionsv1 .NamespaceScoped {
499+ if providerResourceScope == apiextensionsv1 .NamespaceScoped {
472500 obj , err = providerClient .Namespace (providerNs ).Get (ctx , "test" , metav1.GetOptions {})
473501 } else {
474502 obj , err = providerClient .Get (ctx , clusterScopedUpInsName , metav1.GetOptions {})
@@ -478,7 +506,7 @@ func testHappyCase(
478506 value , _ , err = unstructured .NestedString (obj .Object , "spec" , "intent" )
479507
480508 require .NoError (t , err )
481- if resourceScope == apiextensionsv1 .NamespaceScoped {
509+ if consumerResourceScope == apiextensionsv1 .NamespaceScoped {
482510 return value == "Updated cowboy intent"
483511 } else {
484512 return value == "Updated sheriff intent"
@@ -492,14 +520,14 @@ func testHappyCase(
492520 err := retry .RetryOnConflict (retry .DefaultRetry , func () error {
493521 var obj * unstructured.Unstructured
494522 var err error
495- if resourceScope == apiextensionsv1 .NamespaceScoped {
523+ if providerResourceScope == apiextensionsv1 .NamespaceScoped {
496524 obj , err = providerClient .Namespace (providerNs ).Get (ctx , "test" , metav1.GetOptions {})
497525 } else {
498526 obj , err = providerClient .Get (ctx , clusterScopedUpInsName , metav1.GetOptions {})
499527 }
500528 require .NoError (t , err )
501529 unstructured .SetNestedField (obj .Object , "Ready to ride" , "status" , "result" ) //nolint:errcheck
502- if resourceScope == apiextensionsv1 .NamespaceScoped {
530+ if providerResourceScope == apiextensionsv1 .NamespaceScoped {
503531 _ , err = providerClient .Namespace (providerNs ).UpdateStatus (ctx , obj , metav1.UpdateOptions {})
504532 } else {
505533 _ , err = providerClient .UpdateStatus (ctx , obj , metav1.UpdateOptions {})
@@ -511,7 +539,7 @@ func testHappyCase(
511539 require .Eventually (t , func () bool {
512540 var obj * unstructured.Unstructured
513541 var err error
514- if resourceScope == apiextensionsv1 .NamespaceScoped {
542+ if consumerResourceScope == apiextensionsv1 .NamespaceScoped {
515543 obj , err = consumerClient .Namespace (consumerNs ).Get (ctx , "test" , metav1.GetOptions {})
516544 } else {
517545 obj , err = consumerClient .Get (ctx , "test" , metav1.GetOptions {})
@@ -529,13 +557,13 @@ func testHappyCase(
529557 err := retry .RetryOnConflict (retry .DefaultRetry , func () error {
530558 var obj * unstructured.Unstructured
531559 var err error
532- if resourceScope == apiextensionsv1 .NamespaceScoped {
560+ if providerResourceScope == apiextensionsv1 .NamespaceScoped {
533561 obj , err = providerClient .Namespace (providerNs ).Get (ctx , "test" , metav1.GetOptions {})
534562 } else {
535563 obj , err = providerClient .Get (ctx , clusterScopedUpInsName , metav1.GetOptions {})
536564 }
537565 require .NoError (t , err )
538- if resourceScope == apiextensionsv1 .NamespaceScoped {
566+ if providerResourceScope == apiextensionsv1 .NamespaceScoped {
539567 unstructured .SetNestedField (obj .Object , "Drifted cowboy intent" , "spec" , "intent" ) //nolint:errcheck
540568 _ , err = providerClient .Namespace (providerNs ).Update (ctx , obj , metav1.UpdateOptions {})
541569 } else {
@@ -549,14 +577,14 @@ func testHappyCase(
549577 require .Eventually (t , func () bool {
550578 var obj * unstructured.Unstructured
551579 var err error
552- if resourceScope == apiextensionsv1 .NamespaceScoped {
580+ if providerResourceScope == apiextensionsv1 .NamespaceScoped {
553581 obj , err = providerClient .Namespace (providerNs ).Get (ctx , "test" , metav1.GetOptions {})
554582 } else {
555583 obj , err = providerClient .Get (ctx , clusterScopedUpInsName , metav1.GetOptions {})
556584 }
557585 require .NoError (t , err )
558586 var value string
559- if resourceScope == apiextensionsv1 .NamespaceScoped {
587+ if consumerResourceScope == apiextensionsv1 .NamespaceScoped {
560588 value , _ , err = unstructured .NestedString (obj .Object , "spec" , "intent" )
561589 require .NoError (t , err )
562590 return value == "Updated cowboy intent"
@@ -572,7 +600,7 @@ func testHappyCase(
572600 name : "instances deleted downstream are deleted upstream" ,
573601 step : func (t * testing.T ) {
574602 var err error
575- if resourceScope == apiextensionsv1 .NamespaceScoped {
603+ if consumerResourceScope == apiextensionsv1 .NamespaceScoped {
576604 err = consumerClient .Namespace (consumerNs ).Delete (ctx , "test" , metav1.DeleteOptions {})
577605 } else {
578606 err = consumerClient .Delete (ctx , "test" , metav1.DeleteOptions {})
@@ -581,7 +609,7 @@ func testHappyCase(
581609
582610 require .Eventually (t , func () bool {
583611 var err error
584- if resourceScope == apiextensionsv1 .NamespaceScoped {
612+ if providerResourceScope == apiextensionsv1 .NamespaceScoped {
585613 _ , err = providerClient .Namespace (providerNs ).Get (ctx , "test" , metav1.GetOptions {})
586614 } else {
587615 _ , err = providerClient .Get (ctx , clusterScopedUpInsName , metav1.GetOptions {})
@@ -677,3 +705,30 @@ func applyMultiDocYAML(ctx context.Context, t *testing.T, dynamicClient dynamic.
677705
678706 return nil
679707}
708+
709+ func toggleCRDScope (t * testing.T , ctx context.Context , config * rest.Config , name string , scope apiextensionsv1.ResourceScope ) {
710+ clientset , err := apiextensionsclient .NewForConfig (config )
711+ require .NoError (t , err )
712+
713+ crdClient := clientset .ApiextensionsV1 ().CustomResourceDefinitions ()
714+
715+ // copy existing CRD
716+ crd , err := crdClient .Get (ctx , name , metav1.GetOptions {})
717+ require .NoError (t , err )
718+
719+ // delete it
720+ require .NoError (t , crdClient .Delete (ctx , name , metav1.DeleteOptions {}))
721+
722+ require .Eventually (t , func () bool {
723+ _ , err := crdClient .Get (ctx , name , metav1.GetOptions {})
724+ return errors .IsNotFound (err )
725+ }, wait .ForeverTestTimeout , time .Millisecond * 100 , "waiting for the CRD to be deleted" )
726+
727+ // re-create it
728+ crd .Spec .Scope = scope
729+ crd .ObjectMeta .ResourceVersion = ""
730+ crd .ObjectMeta .UID = ""
731+ crd .ObjectMeta .Generation = 0
732+ _ , err = crdClient .Create (ctx , crd , metav1.CreateOptions {})
733+ require .NoError (t , err )
734+ }
0 commit comments