diff --git a/cmd/initializer.go b/cmd/initializer.go index 6fc43aa3..b95714e1 100644 --- a/cmd/initializer.go +++ b/cmd/initializer.go @@ -94,7 +94,7 @@ var initializerCmd = &cobra.Command{ initializerCfg.IDP.AdditionalRedirectURLs = []string{} } - if err := controller.NewLogicalClusterReconciler(log, orgClient, initializerCfg, runtimeClient, mgr). + if err := controller.NewLogicalClusterInitializer(log, orgClient, initializerCfg, runtimeClient, mgr). SetupWithManager(mgr, defaultCfg); err != nil { setupLog.Error(err, "unable to create controller", "controller", "LogicalCluster") os.Exit(1) diff --git a/cmd/operator.go b/cmd/operator.go index 395a62c1..b786552f 100644 --- a/cmd/operator.go +++ b/cmd/operator.go @@ -154,6 +154,14 @@ var operatorCmd = &cobra.Command{ fga := openfgav1.NewOpenFGAServiceClient(conn) + k8sCfg := ctrl.GetConfigOrDie() + + runtimeClient, err := client.New(k8sCfg, client.Options{Scheme: scheme}) + if err != nil { + log.Error().Err(err).Msg("Failed to create in cluster client") + return err + } + if err = controller.NewStoreReconciler(log, fga, mgr). SetupWithManager(mgr, defaultCfg); err != nil { log.Error().Err(err).Str("controller", "store").Msg("unable to create controller") @@ -173,6 +181,11 @@ var operatorCmd = &cobra.Command{ log.Error().Err(err).Str("controller", "invite").Msg("unable to create controller") return err } + + if err = controller.NewLogicalClusterReconciler(log, orgClient, operatorCfg, runtimeClient, mgr).SetupWithManager(mgr, defaultCfg, operatorCfg.InitializerName()); err != nil { + log.Error().Err(err).Str("controller", "logicalcluster").Msg("unable to create controller") + return err + } if err = controller.NewAccountInfoReconciler(log, mgr).SetupWithManager(mgr, defaultCfg); err != nil { log.Error().Err(err).Str("controller", "accountinfo").Msg("unable to create controller") return err diff --git a/internal/controller/initializer_controller.go b/internal/controller/initializer_controller.go index 8a77a2f9..a1ebb973 100644 --- a/internal/controller/initializer_controller.go +++ b/internal/controller/initializer_controller.go @@ -20,14 +20,14 @@ import ( kcpcorev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" ) -type LogicalClusterReconciler struct { +type LogicalClusterInitializer struct { log *logger.Logger mclifecycle *multicluster.LifecycleManager } -func NewLogicalClusterReconciler(log *logger.Logger, orgClient client.Client, cfg config.Config, inClusterClient client.Client, mgr mcmanager.Manager) *LogicalClusterReconciler { - return &LogicalClusterReconciler{ +func NewLogicalClusterInitializer(log *logger.Logger, orgClient client.Client, cfg config.Config, inClusterClient client.Client, mgr mcmanager.Manager) *LogicalClusterInitializer { + return &LogicalClusterInitializer{ log: log, mclifecycle: builder.NewBuilder("logicalcluster", "LogicalClusterReconciler", []lifecyclesubroutine.Subroutine{ subroutine.NewWorkspaceInitializer(orgClient, cfg, mgr), @@ -42,11 +42,11 @@ func NewLogicalClusterReconciler(log *logger.Logger, orgClient client.Client, cf } } -func (r *LogicalClusterReconciler) Reconcile(ctx context.Context, req mcreconcile.Request) (ctrl.Result, error) { +func (r *LogicalClusterInitializer) Reconcile(ctx context.Context, req mcreconcile.Request) (ctrl.Result, error) { ctxWithCluster := mccontext.WithCluster(ctx, req.ClusterName) return r.mclifecycle.Reconcile(ctxWithCluster, req, &kcpcorev1alpha1.LogicalCluster{}) } -func (r *LogicalClusterReconciler) SetupWithManager(mgr mcmanager.Manager, cfg *platformeshconfig.CommonServiceConfig, evp ...predicate.Predicate) error { +func (r *LogicalClusterInitializer) SetupWithManager(mgr mcmanager.Manager, cfg *platformeshconfig.CommonServiceConfig, evp ...predicate.Predicate) error { return r.mclifecycle.SetupWithManager(mgr, cfg.MaxConcurrentReconciles, "LogicalCluster", &kcpcorev1alpha1.LogicalCluster{}, cfg.DebugLabelValue, r, r.log, evp...) } diff --git a/internal/controller/logical_cluster_controller.go b/internal/controller/logical_cluster_controller.go new file mode 100644 index 00000000..735dfbd6 --- /dev/null +++ b/internal/controller/logical_cluster_controller.go @@ -0,0 +1,78 @@ +package controller + +import ( + "context" + "slices" + + platformeshconfig "github.com/platform-mesh/golang-commons/config" + "github.com/platform-mesh/golang-commons/controller/lifecycle/builder" + "github.com/platform-mesh/golang-commons/controller/lifecycle/multicluster" + lifecyclesubroutine "github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine" + "github.com/platform-mesh/golang-commons/logger" + "github.com/platform-mesh/security-operator/internal/config" + "github.com/platform-mesh/security-operator/internal/subroutine" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + mccontext "sigs.k8s.io/multicluster-runtime/pkg/context" + mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" + mcreconcile "sigs.k8s.io/multicluster-runtime/pkg/reconcile" + + kcpcorev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" +) + +type LogicalClusterReconciler struct { + log *logger.Logger + mgr mcmanager.Manager + mclifecycle *multicluster.LifecycleManager +} + +func NewLogicalClusterReconciler(log *logger.Logger, orgClient client.Client, cfg config.Config, inClusterClient client.Client, mgr mcmanager.Manager) *LogicalClusterReconciler { + return &LogicalClusterReconciler{ + log: log, + mgr: mgr, + mclifecycle: builder.NewBuilder("logicalcluster", "LogicalClusterReconciler", []lifecyclesubroutine.Subroutine{ + subroutine.NewWorkspaceInitializer(orgClient, cfg, mgr), + subroutine.NewIDPSubroutine(orgClient, mgr, cfg), + subroutine.NewInviteSubroutine(orgClient, mgr), + subroutine.NewWorkspaceAuthConfigurationSubroutine(orgClient, inClusterClient, mgr, cfg), + }, log).WithReadOnly().BuildMultiCluster(mgr), + } +} + +func (r *LogicalClusterReconciler) Reconcile(ctx context.Context, req mcreconcile.Request) (ctrl.Result, error) { + ctxWithCluster := mccontext.WithCluster(ctx, req.ClusterName) + return r.mclifecycle.Reconcile(ctxWithCluster, req, &kcpcorev1alpha1.LogicalCluster{}) +} + +func (r *LogicalClusterReconciler) SetupWithManager(mgr mcmanager.Manager, cfg *platformeshconfig.CommonServiceConfig, initializerName string, evp ...predicate.Predicate) error { + allPredicates := append([]predicate.Predicate{HasInitializerPredicate(initializerName)}, evp...) + return r.mclifecycle.SetupWithManager(mgr, cfg.MaxConcurrentReconciles, "LogicalCluster", &kcpcorev1alpha1.LogicalCluster{}, cfg.DebugLabelValue, r, r.log, allPredicates...) +} + +func HasInitializerPredicate(initializerName string) predicate.Predicate { + initializer := kcpcorev1alpha1.LogicalClusterInitializer(initializerName) + return predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + lc := e.Object.(*kcpcorev1alpha1.LogicalCluster) + return shouldReconcile(lc, initializer) + }, + UpdateFunc: func(e event.UpdateEvent) bool { + newLC := e.ObjectNew.(*kcpcorev1alpha1.LogicalCluster) + return shouldReconcile(newLC, initializer) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + lc := e.Object.(*kcpcorev1alpha1.LogicalCluster) + return shouldReconcile(lc, initializer) + }, + GenericFunc: func(e event.GenericEvent) bool { + lc := e.Object.(*kcpcorev1alpha1.LogicalCluster) + return shouldReconcile(lc, initializer) + }, + } +} + +func shouldReconcile(lc *kcpcorev1alpha1.LogicalCluster, initializer kcpcorev1alpha1.LogicalClusterInitializer) bool { + return slices.Contains(lc.Spec.Initializers, initializer) && !slices.Contains(lc.Status.Initializers, initializer) +} diff --git a/internal/subroutine/idp.go b/internal/subroutine/idp.go index 4f66b562..f0e00cb3 100644 --- a/internal/subroutine/idp.go +++ b/internal/subroutine/idp.go @@ -3,7 +3,6 @@ package subroutine import ( "context" "fmt" - "slices" "strings" accountv1alpha1 "github.com/platform-mesh/account-operator/api/v1alpha1" @@ -92,32 +91,28 @@ func (i *IDPSubroutine) Process(ctx context.Context, instance runtimeobject.Runt return ctrl.Result{}, nil } - clients := []v1alpha1.IdentityProviderClientConfig{ - { - ClientName: workspaceName, - ClientType: v1alpha1.IdentityProviderClientTypeConfidential, - RedirectURIs: append(i.additionalRedirectURLs, fmt.Sprintf("https://%s.%s/*", workspaceName, i.baseDomain)), - PostLogoutRedirectURIs: []string{fmt.Sprintf("https://%s.%s/logout*", workspaceName, i.baseDomain)}, - SecretRef: corev1.SecretReference{ - Name: fmt.Sprintf("portal-client-secret-%s-%s", workspaceName, workspaceName), - Namespace: secretNamespace, + idp := &v1alpha1.IdentityProviderConfiguration{ObjectMeta: metav1.ObjectMeta{Name: workspaceName}} + _, err = controllerutil.CreateOrUpdate(ctx, cl.GetClient(), idp, func() error { + idp.Spec.Clients = []v1alpha1.IdentityProviderClientConfig{ + { + ClientName: workspaceName, + ClientType: v1alpha1.IdentityProviderClientTypeConfidential, + RedirectURIs: append(i.additionalRedirectURLs, fmt.Sprintf("https://%s.%s/*", workspaceName, i.baseDomain)), + PostLogoutRedirectURIs: []string{fmt.Sprintf("https://%s.%s/logout*", workspaceName, i.baseDomain)}, + SecretRef: corev1.SecretReference{ + Name: fmt.Sprintf("portal-client-secret-%s-%s", workspaceName, workspaceName), + Namespace: secretNamespace, + }, }, - }, - { - ClientName: kubectlClientName, - ClientType: v1alpha1.IdentityProviderClientTypePublic, - RedirectURIs: i.kubectlClientRedirectURLs, - SecretRef: corev1.SecretReference{ - Name: fmt.Sprintf("portal-client-secret-%s-%s", workspaceName, kubectlClientName), - Namespace: secretNamespace, + { + ClientName: kubectlClientName, + ClientType: v1alpha1.IdentityProviderClientTypePublic, + RedirectURIs: i.kubectlClientRedirectURLs, + SecretRef: corev1.SecretReference{ + Name: fmt.Sprintf("portal-client-secret-%s-%s", workspaceName, kubectlClientName), + Namespace: secretNamespace, + }, }, - }, - } - - idp := &v1alpha1.IdentityProviderConfiguration{ObjectMeta: metav1.ObjectMeta{Name: workspaceName}} - _, err = controllerutil.CreateOrPatch(ctx, cl.GetClient(), idp, func() error { - for _, desired := range clients { - idp.Spec.Clients = ensureClient(idp.Spec.Clients, desired) } return nil }) @@ -195,24 +190,6 @@ func (i *IDPSubroutine) patchAccountInfo(ctx context.Context, cl client.Client, return nil } -// ensureClient updates only fields managed by this subroutine, preserving ClientID and RegistrationClientURI -// that are set by reconciling an idp resource -func ensureClient(existing []v1alpha1.IdentityProviderClientConfig, desired v1alpha1.IdentityProviderClientConfig) []v1alpha1.IdentityProviderClientConfig { - idx := slices.IndexFunc(existing, func(c v1alpha1.IdentityProviderClientConfig) bool { - return c.ClientName == desired.ClientName - }) - - if idx != -1 { - existing[idx].ClientType = desired.ClientType - existing[idx].RedirectURIs = desired.RedirectURIs - existing[idx].PostLogoutRedirectURIs = desired.PostLogoutRedirectURIs - existing[idx].SecretRef = desired.SecretRef - return existing - } - - return append(existing, desired) -} - func getWorkspaceName(lc *kcpcorev1alpha1.LogicalCluster) string { if path, ok := lc.Annotations["kcp.io/path"]; ok { pathElements := strings.Split(path, ":")