@@ -16,12 +16,10 @@ import (
1616 "k8s.io/apimachinery/pkg/runtime/schema"
1717 "k8s.io/apimachinery/pkg/types"
1818 "k8s.io/apimachinery/pkg/util/sets"
19- "k8s.io/client-go/tools/clientcmd"
2019 "k8s.io/client-go/tools/record"
2120 ctrl "sigs.k8s.io/controller-runtime"
2221 "sigs.k8s.io/controller-runtime/pkg/builder"
2322 "sigs.k8s.io/controller-runtime/pkg/client"
24- "sigs.k8s.io/controller-runtime/pkg/client/fake"
2523 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
2624 "sigs.k8s.io/controller-runtime/pkg/handler"
2725 "sigs.k8s.io/controller-runtime/pkg/predicate"
@@ -37,13 +35,14 @@ import (
3735 ctrlutils "github.com/openmcp-project/controller-utils/pkg/controller"
3836 errutils "github.com/openmcp-project/controller-utils/pkg/errors"
3937 "github.com/openmcp-project/controller-utils/pkg/logging"
38+ testutils "github.com/openmcp-project/controller-utils/pkg/testing"
4039
4140 clustersv1alpha1 "github.com/openmcp-project/openmcp-operator/api/clusters/v1alpha1"
4241 clusterconst "github.com/openmcp-project/openmcp-operator/api/clusters/v1alpha1/constants"
4342 commonapi "github.com/openmcp-project/openmcp-operator/api/common"
4443 openmcpconst "github.com/openmcp-project/openmcp-operator/api/constants"
4544 providerv1alpha1 "github.com/openmcp-project/openmcp-operator/api/provider/v1alpha1"
46- accesslib "github.com/openmcp-project/openmcp-operator/lib/clusteraccess"
45+ accesslib "github.com/openmcp-project/openmcp-operator/lib/clusteraccess/advanced "
4746
4847 dnsv1alpha1 "github.com/openmcp-project/platform-service-dns/api/dns/v1alpha1"
4948)
@@ -56,17 +55,19 @@ const (
5655 SourceKindHelmRepository = "HelmRepository"
5756 SourceKindGitRepository = "GitRepository"
5857 SourceKindOCIRepository = "OCIRepository"
58+
59+ clusterId = "cluster"
5960)
6061
6162type ClusterReconciler struct {
62- PlatformCluster * clusters.Cluster
63- eventRecorder record.EventRecorder
64- ProviderName string
65- ProviderNamespace string
66- Environment string
67- KnownClusters map [types.NamespacedName ]struct {}
68- KnownClustersLock * sync.RWMutex
69- FakeClientMappings map [ string ]client. Client // only used for testing
63+ PlatformCluster * clusters.Cluster
64+ eventRecorder record.EventRecorder
65+ ProviderName string
66+ ProviderNamespace string
67+ Environment string
68+ KnownClusters map [types.NamespacedName ]struct {}
69+ KnownClustersLock * sync.RWMutex
70+ ClusterAccessReconciler accesslib. ClusterAccessReconciler
7071}
7172
7273func NewClusterReconciler (platformCluster * clusters.Cluster , recorder record.EventRecorder , providerName , providerNamespace , environment string ) * ClusterReconciler {
@@ -78,6 +79,22 @@ func NewClusterReconciler(platformCluster *clusters.Cluster, recorder record.Eve
7879 Environment : environment ,
7980 KnownClusters : map [types.NamespacedName ]struct {}{},
8081 KnownClustersLock : & sync.RWMutex {},
82+ ClusterAccessReconciler : accesslib .NewClusterAccessReconciler (platformCluster .Client (), ControllerName ).
83+ WithManagedLabels (func (controllerName string , req reconcile.Request , _ accesslib.ClusterRegistration ) (string , string , map [string ]string ) {
84+ return fmt .Sprintf ("%s.%s" , providerName , controllerName ), req .Name , nil
85+ }).
86+ Register (accesslib .ExistingCluster (clusterId , "" , accesslib .IdentityReferenceGenerator ).
87+ WithTokenAccess (& clustersv1alpha1.TokenConfig {
88+ RoleRefs : []commonapi.RoleRef {
89+ {
90+ Kind : "ClusterRole" ,
91+ Name : "cluster-admin" ,
92+ },
93+ },
94+ }).
95+ WithNamespaceGenerator (accesslib .RequestNamespaceGenerator ).
96+ Build (),
97+ ),
8198 }
8299}
83100
@@ -223,80 +240,28 @@ func (r *ClusterReconciler) handleCreateOrUpdate(ctx context.Context, c *cluster
223240 r .addKnownCluster (c )
224241
225242 log .Info ("Creating or updating AccessRequest to get access to Cluster" )
226- ar := & clustersv1alpha1.AccessRequest {}
227- ar .SetName (accesslib .StableRequestNameFromLocalName (ControllerName , c .Name ))
228- ar .SetNamespace (c .Namespace )
229- if _ , err := controllerutil .CreateOrUpdate (ctx , r .PlatformCluster .Client (), ar , func () error {
230- if err := controllerutil .SetOwnerReference (c , ar , r .PlatformCluster .Scheme ()); err != nil {
231- return fmt .Errorf ("error setting owner reference: %w" , err )
232- }
233- ar .Labels = maputils .Merge (ar .Labels , expectedLabels )
234- ar .Spec .ClusterRef = & commonapi.ObjectReference {
235- Name : c .Name ,
236- Namespace : c .Namespace ,
237- }
238- ar .Spec .Token = & clustersv1alpha1.TokenConfig {
239- RoleRefs : []commonapi.RoleRef {
240- {
241- Kind : "ClusterRole" ,
242- Name : "cluster-admin" ,
243- },
244- },
245- }
246- return nil
247- }); err != nil {
248- rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error creating or updating AccessRequest '%s/%s': %w" , ar .Namespace , ar .Name , err ), clusterconst .ReasonPlatformClusterInteractionProblem )
249- return rr
250- }
251- if err := r .PlatformCluster .Client ().Get (ctx , client .ObjectKeyFromObject (ar ), ar ); err != nil {
252- rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error getting AccessRequest '%s/%s': %w" , ar .Namespace , ar .Name , err ), clusterconst .ReasonPlatformClusterInteractionProblem )
243+ req := testutils .RequestFromObject (c )
244+ res , err := r .ClusterAccessReconciler .Reconcile (ctx , req )
245+ if err != nil {
246+ rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error reconciling cluster access: %w" , err ), clusterconst .ReasonInternalError )
253247 return rr
254248 }
255- if ar .Status .IsDenied () {
256- rr .Message = fmt .Sprintf ("AccessRequest '%s/%s' was denied, unable to proceed with deploying DNS configuration" , ar .Namespace , ar .Name )
249+ if res .RequeueAfter > 0 {
250+ log .Info ("Requeuing because cluster access is not yet available" , "after" , res .RequeueAfter )
251+ rr .Result = res
257252 return rr
258253 }
259- if ! ar .Status .IsGranted () {
260- rr .Message = fmt .Sprintf ("AccessRequest '%s/%s' is not yet granted, waiting for access to be granted" , ar .Namespace , ar .Name )
261- rr .Result .RequeueAfter = defaultRequeueAfterDuration
262- return rr
254+ ar , err := r .ClusterAccessReconciler .AccessRequest (ctx , req , clusterId )
255+ if err != nil {
256+ rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error getting AccessRequest: %w" , err ), clusterconst .ReasonInternalError )
263257 }
264258 rr .AccessRequest = ar
265-
266- // get access to Cluster
267- if ar .Status .SecretRef == nil {
268- rr .Message = fmt .Sprintf ("AccessRequest '%s/%s' does not have a secretRef in its status despite being granted" , ar .Namespace , ar .Name )
269- return rr
270- }
271- sec := & corev1.Secret {}
272- sec .Name = ar .Status .SecretRef .Name
273- sec .Namespace = ar .Status .SecretRef .Namespace
274- if err := r .PlatformCluster .Client ().Get (ctx , client .ObjectKeyFromObject (sec ), sec ); err != nil {
275- rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error getting secret '%s/%s' referenced by AccessRequest '%s/%s': %w" , sec .Namespace , sec .Name , ar .Namespace , ar .Name , err ), clusterconst .ReasonPlatformClusterInteractionProblem )
259+ access , err := r .ClusterAccessReconciler .Access (ctx , req , clusterId )
260+ if err != nil {
261+ rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error getting access to Cluster: %w" , err ), clusterconst .ReasonInternalError )
276262 return rr
277263 }
278- kcfgData := sec .Data [clustersv1alpha1 .SecretKeyKubeconfig ]
279- if strings .HasPrefix (string (kcfgData ), "fake:" ) && r .FakeClientMappings != nil {
280- log .Info ("Using fake client for testing, this message should never appear outside of tests" )
281- id := strings .TrimPrefix (string (kcfgData ), "fake:" )
282- fk := r .FakeClientMappings [id ]
283- if fk == nil {
284- fk = fake .NewFakeClient ()
285- r .FakeClientMappings [id ] = fk
286- }
287- rr .Access = clusters .NewTestClusterFromClient (id , fk )
288- } else {
289- rest , err := clientcmd .RESTConfigFromKubeConfig (kcfgData )
290- if err != nil {
291- rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error creating REST config for Cluster from kubeconfig in secret '%s/%s': %w" , sec .Namespace , sec .Name , err ), clusterconst .ReasonInternalError )
292- return rr
293- }
294- rr .Access = clusters .New (ar .Name ).WithRESTConfig (rest )
295- if err := rr .Access .InitializeClient (nil ); err != nil {
296- rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error initializing client for Cluster from kubeconfig in secret '%s/%s': %w" , sec .Namespace , sec .Name , err ), clusterconst .ReasonInternalError )
297- return rr
298- }
299- }
264+ rr .Access = access
300265
301266 rr , copied := r .copySecrets (ctx , c .Namespace , expectedLabels , rr , platformClusterCopy )
302267 if rr .ReconcileError != nil || rr .Result .RequeueAfter > 0 {
@@ -354,55 +319,19 @@ func (r *ClusterReconciler) handleDelete(ctx context.Context, c *clustersv1alpha
354319 }
355320
356321 // get access to Cluster
357- accessRequestGone := false
358- ar := & clustersv1alpha1.AccessRequest {}
359- ar .SetName (accesslib .StableRequestNameFromLocalName (ControllerName , c .Name ))
360- ar .SetNamespace (c .Namespace )
361- if err := r .PlatformCluster .Client ().Get (ctx , client .ObjectKeyFromObject (ar ), ar ); err != nil {
362- if ! apierrors .IsNotFound (err ) {
363- rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error getting AccessRequest '%s/%s': %w" , ar .Namespace , ar .Name , err ), clusterconst .ReasonPlatformClusterInteractionProblem )
364- return rr
365- }
366- accessRequestGone = true
322+ req := testutils .RequestFromObject (c )
323+ ar , err := r .ClusterAccessReconciler .AccessRequest (ctx , req , clusterId )
324+ if client .IgnoreNotFound (err ) != nil {
325+ rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error getting AccessRequest: %w" , err ), clusterconst .ReasonInternalError )
326+ return rr
367327 }
368- if ! accessRequestGone && ar .Status .IsGranted () {
369- if ar .Status .SecretRef == nil {
370- rr .Message = fmt .Sprintf ("AccessRequest '%s/%s' does not have a secretRef in its status despite being granted" , ar .Namespace , ar .Name )
371- return rr
372- }
373- sec := & corev1.Secret {}
374- sec .Name = ar .Status .SecretRef .Name
375- sec .Namespace = ar .Status .SecretRef .Namespace
376- if err := r .PlatformCluster .Client ().Get (ctx , client .ObjectKeyFromObject (sec ), sec ); err != nil {
377- rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error getting secret '%s/%s' referenced by AccessRequest '%s/%s': %w" , sec .Namespace , sec .Name , ar .Namespace , ar .Name , err ), clusterconst .ReasonPlatformClusterInteractionProblem )
328+ if ar != nil && ar .Status .IsGranted () {
329+ access , err := r .ClusterAccessReconciler .Access (ctx , req , clusterId )
330+ if err != nil {
331+ rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error getting access to Cluster: %w" , err ), clusterconst .ReasonInternalError )
378332 return rr
379333 }
380- kcfgData := sec .Data [clustersv1alpha1 .SecretKeyKubeconfig ]
381- if strings .HasPrefix (string (kcfgData ), "fake:" ) && r .FakeClientMappings != nil {
382- log .Info ("Using fake client for testing, this message should never appear outside of tests" )
383- id := strings .TrimPrefix (string (kcfgData ), "fake:" )
384- fk := r .FakeClientMappings [id ]
385- if fk == nil {
386- fk = fake .NewFakeClient ()
387- r .FakeClientMappings [id ] = fk
388- }
389- rr .Access = clusters .NewTestClusterFromClient (id , fk )
390- } else {
391- rest , err := clientcmd .RESTConfigFromKubeConfig (kcfgData )
392- if err != nil {
393- rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error creating REST config for Cluster from kubeconfig in secret '%s/%s': %w" , sec .Namespace , sec .Name , err ), clusterconst .ReasonInternalError )
394- return rr
395- }
396- rr .Access = clusters .New (ar .Name ).WithRESTConfig (rest )
397- if err := rr .Access .InitializeRESTConfig (); err != nil {
398- rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error initializing REST config for Cluster from kubeconfig in secret '%s/%s': %w" , sec .Namespace , sec .Name , err ), clusterconst .ReasonInternalError )
399- return rr
400- }
401- if err := rr .Access .InitializeClient (nil ); err != nil {
402- rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error initializing client for Cluster from kubeconfig in secret '%s/%s': %w" , sec .Namespace , sec .Name , err ), clusterconst .ReasonInternalError )
403- return rr
404- }
405- }
334+ rr .Access = access
406335
407336 rr = r .removeSecrets (ctx , TargetClusterNamespace , expectedLabels , rr , targetClusterCopy , nil )
408337 if rr .ReconcileError != nil || rr .Result .RequeueAfter > 0 {
@@ -422,12 +351,15 @@ func (r *ClusterReconciler) handleDelete(ctx context.Context, c *clustersv1alpha
422351 return rr
423352 }
424353
425- // delete AccessRequest
426- if err := r .PlatformCluster .Client ().Delete (ctx , ar ); err != nil {
427- if ! apierrors .IsNotFound (err ) {
428- rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error deleting AccessRequest '%s/%s': %w" , ar .Namespace , ar .Name , err ), clusterconst .ReasonPlatformClusterInteractionProblem )
429- return rr
430- }
354+ res , err := r .ClusterAccessReconciler .ReconcileDelete (ctx , req )
355+ if err != nil {
356+ rr .ReconcileError = errutils .WithReason (fmt .Errorf ("error reconciling deletion of cluster access: %w" , err ), clusterconst .ReasonInternalError )
357+ return rr
358+ }
359+ if res .RequeueAfter > 0 {
360+ log .Info ("Requeuing because cluster access has not been fully deleted yet" , "after" , res .RequeueAfter )
361+ rr .Result = res
362+ return rr
431363 }
432364
433365 // remove finalizer from Cluster
0 commit comments