Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ require (
github.com/fluxcd/pkg/apis/meta v1.22.0
github.com/fluxcd/source-controller/api v1.7.2
github.com/openmcp-project/controller-utils v0.23.1
github.com/openmcp-project/openmcp-operator/api v0.15.1
github.com/openmcp-project/openmcp-operator/lib v0.15.1
github.com/openmcp-project/openmcp-operator/api v0.15.2
github.com/openmcp-project/openmcp-operator/lib v0.15.3-0.20251017065940-637b58a6e264
github.com/openmcp-project/platform-service-dns/api v0.0.2
github.com/spf13/cobra v1.10.1
k8s.io/api v0.34.1
Expand Down Expand Up @@ -106,7 +106,7 @@ require (
k8s.io/component-base v0.34.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3 // indirect
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
Expand Down
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/openmcp-project/controller-utils v0.23.1 h1:suuZ9UWJbSE/LbpZCtzpHg6FOqU7BkR4bq6cEAYykyc=
github.com/openmcp-project/controller-utils v0.23.1/go.mod h1:fU16gy7PHCqMtNaH/nAnHJyGM1SI5ZUiyQcUwrPy2TQ=
github.com/openmcp-project/openmcp-operator/api v0.15.1 h1:xjVQG9zt+QGuSyhGCE0HV0ZCVS9KTX71JALoCUM4BRU=
github.com/openmcp-project/openmcp-operator/api v0.15.1/go.mod h1:eaJf9f3D/SuSF8souPpatSxt67FJkyGFYfKGIEQ2qvA=
github.com/openmcp-project/openmcp-operator/lib v0.15.1 h1:Sqb60JylpodWhhaKAbb4Uk33j/LufimDt9JyfSRnRAM=
github.com/openmcp-project/openmcp-operator/lib v0.15.1/go.mod h1:apLlI2djouLHfv/ZrZM7KoFZwXDTi0GcYQbVVdvRRDw=
github.com/openmcp-project/openmcp-operator/api v0.15.2 h1:Ujf0NLysUSj0Wiel3qnroDcnnHCXMEpbFd0a9rkZoxY=
github.com/openmcp-project/openmcp-operator/api v0.15.2/go.mod h1:0KytEWVi1Gw5SEjyclhNZmUXks+SqbivLW10fDe7vL4=
github.com/openmcp-project/openmcp-operator/lib v0.15.3-0.20251017065940-637b58a6e264 h1:Tb9voXDeiYc4vnkMwEFX7Np9tZQk1XpV6FQ4YB0QcTs=
github.com/openmcp-project/openmcp-operator/lib v0.15.3-0.20251017065940-637b58a6e264/go.mod h1:D/CwGD8NjUHba4PAIzEHFi/FNoj4xcnOb6F2qwcAaIc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down Expand Up @@ -286,8 +286,8 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3 h1:liMHz39T5dJO1aOKHLvwaCjDbf07wVh6yaUlTpunnkE=
k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0=
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y=
Expand Down
190 changes: 61 additions & 129 deletions internal/controllers/cluster/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
Expand All @@ -37,13 +35,14 @@ import (
ctrlutils "github.com/openmcp-project/controller-utils/pkg/controller"
errutils "github.com/openmcp-project/controller-utils/pkg/errors"
"github.com/openmcp-project/controller-utils/pkg/logging"
testutils "github.com/openmcp-project/controller-utils/pkg/testing"

clustersv1alpha1 "github.com/openmcp-project/openmcp-operator/api/clusters/v1alpha1"
clusterconst "github.com/openmcp-project/openmcp-operator/api/clusters/v1alpha1/constants"
commonapi "github.com/openmcp-project/openmcp-operator/api/common"
openmcpconst "github.com/openmcp-project/openmcp-operator/api/constants"
providerv1alpha1 "github.com/openmcp-project/openmcp-operator/api/provider/v1alpha1"
accesslib "github.com/openmcp-project/openmcp-operator/lib/clusteraccess"
accesslib "github.com/openmcp-project/openmcp-operator/lib/clusteraccess/advanced"

dnsv1alpha1 "github.com/openmcp-project/platform-service-dns/api/dns/v1alpha1"
)
Expand All @@ -56,17 +55,19 @@ const (
SourceKindHelmRepository = "HelmRepository"
SourceKindGitRepository = "GitRepository"
SourceKindOCIRepository = "OCIRepository"

clusterId = "cluster"
)

type ClusterReconciler struct {
PlatformCluster *clusters.Cluster
eventRecorder record.EventRecorder
ProviderName string
ProviderNamespace string
Environment string
KnownClusters map[types.NamespacedName]struct{}
KnownClustersLock *sync.RWMutex
FakeClientMappings map[string]client.Client // only used for testing
PlatformCluster *clusters.Cluster
eventRecorder record.EventRecorder
ProviderName string
ProviderNamespace string
Environment string
KnownClusters map[types.NamespacedName]struct{}
KnownClustersLock *sync.RWMutex
ClusterAccessReconciler accesslib.ClusterAccessReconciler
}

func NewClusterReconciler(platformCluster *clusters.Cluster, recorder record.EventRecorder, providerName, providerNamespace, environment string) *ClusterReconciler {
Expand All @@ -78,6 +79,22 @@ func NewClusterReconciler(platformCluster *clusters.Cluster, recorder record.Eve
Environment: environment,
KnownClusters: map[types.NamespacedName]struct{}{},
KnownClustersLock: &sync.RWMutex{},
ClusterAccessReconciler: accesslib.NewClusterAccessReconciler(platformCluster.Client(), ControllerName).
WithManagedLabels(func(controllerName string, req reconcile.Request, _ accesslib.ClusterRegistration) (string, string, map[string]string) {
return fmt.Sprintf("%s.%s", providerName, controllerName), req.Name, nil
}).
Register(accesslib.ExistingCluster(clusterId, "", accesslib.IdentityReferenceGenerator).
WithTokenAccess(&clustersv1alpha1.TokenConfig{
RoleRefs: []commonapi.RoleRef{
{
Kind: "ClusterRole",
Name: "cluster-admin",
},
},
}).
WithNamespaceGenerator(accesslib.RequestNamespaceGenerator).
Build(),
),
}
}

Expand Down Expand Up @@ -223,80 +240,28 @@ func (r *ClusterReconciler) handleCreateOrUpdate(ctx context.Context, c *cluster
r.addKnownCluster(c)

log.Info("Creating or updating AccessRequest to get access to Cluster")
ar := &clustersv1alpha1.AccessRequest{}
ar.SetName(accesslib.StableRequestNameFromLocalName(ControllerName, c.Name))
ar.SetNamespace(c.Namespace)
if _, err := controllerutil.CreateOrUpdate(ctx, r.PlatformCluster.Client(), ar, func() error {
if err := controllerutil.SetOwnerReference(c, ar, r.PlatformCluster.Scheme()); err != nil {
return fmt.Errorf("error setting owner reference: %w", err)
}
ar.Labels = maputils.Merge(ar.Labels, expectedLabels)
ar.Spec.ClusterRef = &commonapi.ObjectReference{
Name: c.Name,
Namespace: c.Namespace,
}
ar.Spec.Token = &clustersv1alpha1.TokenConfig{
RoleRefs: []commonapi.RoleRef{
{
Kind: "ClusterRole",
Name: "cluster-admin",
},
},
}
return nil
}); err != nil {
rr.ReconcileError = errutils.WithReason(fmt.Errorf("error creating or updating AccessRequest '%s/%s': %w", ar.Namespace, ar.Name, err), clusterconst.ReasonPlatformClusterInteractionProblem)
return rr
}
if err := r.PlatformCluster.Client().Get(ctx, client.ObjectKeyFromObject(ar), ar); err != nil {
rr.ReconcileError = errutils.WithReason(fmt.Errorf("error getting AccessRequest '%s/%s': %w", ar.Namespace, ar.Name, err), clusterconst.ReasonPlatformClusterInteractionProblem)
req := testutils.RequestFromObject(c)
res, err := r.ClusterAccessReconciler.Reconcile(ctx, req)
if err != nil {
rr.ReconcileError = errutils.WithReason(fmt.Errorf("error reconciling cluster access: %w", err), clusterconst.ReasonInternalError)
return rr
}
if ar.Status.IsDenied() {
rr.Message = fmt.Sprintf("AccessRequest '%s/%s' was denied, unable to proceed with deploying DNS configuration", ar.Namespace, ar.Name)
if res.RequeueAfter > 0 {
log.Info("Requeuing because cluster access is not yet available", "after", res.RequeueAfter)
rr.Result = res
return rr
}
if !ar.Status.IsGranted() {
rr.Message = fmt.Sprintf("AccessRequest '%s/%s' is not yet granted, waiting for access to be granted", ar.Namespace, ar.Name)
rr.Result.RequeueAfter = defaultRequeueAfterDuration
return rr
ar, err := r.ClusterAccessReconciler.AccessRequest(ctx, req, clusterId)
if err != nil {
rr.ReconcileError = errutils.WithReason(fmt.Errorf("error getting AccessRequest: %w", err), clusterconst.ReasonInternalError)
}
rr.AccessRequest = ar

// get access to Cluster
if ar.Status.SecretRef == nil {
rr.Message = fmt.Sprintf("AccessRequest '%s/%s' does not have a secretRef in its status despite being granted", ar.Namespace, ar.Name)
return rr
}
sec := &corev1.Secret{}
sec.Name = ar.Status.SecretRef.Name
sec.Namespace = ar.Status.SecretRef.Namespace
if err := r.PlatformCluster.Client().Get(ctx, client.ObjectKeyFromObject(sec), sec); err != nil {
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)
access, err := r.ClusterAccessReconciler.Access(ctx, req, clusterId)
if err != nil {
rr.ReconcileError = errutils.WithReason(fmt.Errorf("error getting access to Cluster: %w", err), clusterconst.ReasonInternalError)
return rr
}
kcfgData := sec.Data[clustersv1alpha1.SecretKeyKubeconfig]
if strings.HasPrefix(string(kcfgData), "fake:") && r.FakeClientMappings != nil {
log.Info("Using fake client for testing, this message should never appear outside of tests")
id := strings.TrimPrefix(string(kcfgData), "fake:")
fk := r.FakeClientMappings[id]
if fk == nil {
fk = fake.NewFakeClient()
r.FakeClientMappings[id] = fk
}
rr.Access = clusters.NewTestClusterFromClient(id, fk)
} else {
rest, err := clientcmd.RESTConfigFromKubeConfig(kcfgData)
if err != nil {
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)
return rr
}
rr.Access = clusters.New(ar.Name).WithRESTConfig(rest)
if err := rr.Access.InitializeClient(nil); err != nil {
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)
return rr
}
}
rr.Access = access

rr, copied := r.copySecrets(ctx, c.Namespace, expectedLabels, rr, platformClusterCopy)
if rr.ReconcileError != nil || rr.Result.RequeueAfter > 0 {
Expand Down Expand Up @@ -354,55 +319,19 @@ func (r *ClusterReconciler) handleDelete(ctx context.Context, c *clustersv1alpha
}

// get access to Cluster
accessRequestGone := false
ar := &clustersv1alpha1.AccessRequest{}
ar.SetName(accesslib.StableRequestNameFromLocalName(ControllerName, c.Name))
ar.SetNamespace(c.Namespace)
if err := r.PlatformCluster.Client().Get(ctx, client.ObjectKeyFromObject(ar), ar); err != nil {
if !apierrors.IsNotFound(err) {
rr.ReconcileError = errutils.WithReason(fmt.Errorf("error getting AccessRequest '%s/%s': %w", ar.Namespace, ar.Name, err), clusterconst.ReasonPlatformClusterInteractionProblem)
return rr
}
accessRequestGone = true
req := testutils.RequestFromObject(c)
ar, err := r.ClusterAccessReconciler.AccessRequest(ctx, req, clusterId)
if client.IgnoreNotFound(err) != nil {
rr.ReconcileError = errutils.WithReason(fmt.Errorf("error getting AccessRequest: %w", err), clusterconst.ReasonInternalError)
return rr
}
if !accessRequestGone && ar.Status.IsGranted() {
if ar.Status.SecretRef == nil {
rr.Message = fmt.Sprintf("AccessRequest '%s/%s' does not have a secretRef in its status despite being granted", ar.Namespace, ar.Name)
return rr
}
sec := &corev1.Secret{}
sec.Name = ar.Status.SecretRef.Name
sec.Namespace = ar.Status.SecretRef.Namespace
if err := r.PlatformCluster.Client().Get(ctx, client.ObjectKeyFromObject(sec), sec); err != nil {
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)
if ar != nil && ar.Status.IsGranted() {
access, err := r.ClusterAccessReconciler.Access(ctx, req, clusterId)
if err != nil {
rr.ReconcileError = errutils.WithReason(fmt.Errorf("error getting access to Cluster: %w", err), clusterconst.ReasonInternalError)
return rr
}
kcfgData := sec.Data[clustersv1alpha1.SecretKeyKubeconfig]
if strings.HasPrefix(string(kcfgData), "fake:") && r.FakeClientMappings != nil {
log.Info("Using fake client for testing, this message should never appear outside of tests")
id := strings.TrimPrefix(string(kcfgData), "fake:")
fk := r.FakeClientMappings[id]
if fk == nil {
fk = fake.NewFakeClient()
r.FakeClientMappings[id] = fk
}
rr.Access = clusters.NewTestClusterFromClient(id, fk)
} else {
rest, err := clientcmd.RESTConfigFromKubeConfig(kcfgData)
if err != nil {
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)
return rr
}
rr.Access = clusters.New(ar.Name).WithRESTConfig(rest)
if err := rr.Access.InitializeRESTConfig(); err != nil {
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)
return rr
}
if err := rr.Access.InitializeClient(nil); err != nil {
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)
return rr
}
}
rr.Access = access

rr = r.removeSecrets(ctx, TargetClusterNamespace, expectedLabels, rr, targetClusterCopy, nil)
if rr.ReconcileError != nil || rr.Result.RequeueAfter > 0 {
Expand All @@ -422,12 +351,15 @@ func (r *ClusterReconciler) handleDelete(ctx context.Context, c *clustersv1alpha
return rr
}

// delete AccessRequest
if err := r.PlatformCluster.Client().Delete(ctx, ar); err != nil {
if !apierrors.IsNotFound(err) {
rr.ReconcileError = errutils.WithReason(fmt.Errorf("error deleting AccessRequest '%s/%s': %w", ar.Namespace, ar.Name, err), clusterconst.ReasonPlatformClusterInteractionProblem)
return rr
}
res, err := r.ClusterAccessReconciler.ReconcileDelete(ctx, req)
if err != nil {
rr.ReconcileError = errutils.WithReason(fmt.Errorf("error reconciling deletion of cluster access: %w", err), clusterconst.ReasonInternalError)
return rr
}
if res.RequeueAfter > 0 {
log.Info("Requeuing because cluster access has not been fully deleted yet", "after", res.RequeueAfter)
rr.Result = res
return rr
}

// remove finalizer from Cluster
Expand Down
Loading