diff --git a/Taskfile.yaml b/Taskfile.yaml index 54447cd4..4d0299b3 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -8,7 +8,7 @@ vars: CRD_DIRECTORY: config/crd/bases KCP_APIGEN_VERSION: v0.29.0 KCP_VERSION: 0.29.0 - GOLANGCI_LINT_VERSION: v2.8.0 + GOLANGCI_LINT_VERSION: v2.10.1 GOARCH: sh: go env GOARCH GOOS: diff --git a/cmd/initializer.go b/cmd/initializer.go index e3005736..cf937dda 100644 --- a/cmd/initializer.go +++ b/cmd/initializer.go @@ -24,7 +24,6 @@ import ( "k8s.io/client-go/rest" "github.com/kcp-dev/logicalcluster/v3" - mcclient "github.com/kcp-dev/multicluster-provider/client" "github.com/kcp-dev/multicluster-provider/initializingworkspaces" ) @@ -100,18 +99,12 @@ var initializerCmd = &cobra.Command{ initializerCfg.IDP.AdditionalRedirectURLs = []string{} } - if err := controller.NewOrgLogicalClusterReconciler(log, orgClient, initializerCfg, runtimeClient, mgr). + if err := controller.NewOrgLogicalClusterInitializer(log, orgClient, initializerCfg, runtimeClient, mgr). SetupWithManager(mgr, defaultCfg, predicates.LogicalClusterIsAccountTypeOrg()); err != nil { setupLog.Error(err, "unable to create controller", "controller", "LogicalCluster") os.Exit(1) } - kcpCfg, err := getKubeconfigFromPath(initializerCfg.KCP.Kubeconfig) - if err != nil { - log.Error().Err(err).Msg("unable to get KCP kubeconfig") - return err - } - conn, err := grpc.NewClient(initializerCfg.FGA.Target, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Error().Err(err).Msg("unable to create grpc client") @@ -120,12 +113,7 @@ var initializerCmd = &cobra.Command{ defer func() { _ = conn.Close() }() fga := openfgav1.NewOpenFGAServiceClient(conn) - mcc, err := mcclient.New(kcpCfg, client.Options{Scheme: scheme}) - if err != nil { - log.Error().Err(err).Msg("Failed to create multicluster client") - os.Exit(1) - } - if err := controller.NewAccountLogicalClusterReconciler(log, initializerCfg, fga, mcc, mgr). + if err := controller.NewAccountLogicalClusterInitializer(log, initializerCfg, fga, mgr). SetupWithManager(mgr, defaultCfg, predicate.Not(predicates.LogicalClusterIsAccountTypeOrg())); err != nil { setupLog.Error(err, "unable to create controller", "controller", "AccountLogicalCluster") os.Exit(1) diff --git a/cmd/operator.go b/cmd/operator.go index 7171d3df..97addfe9 100644 --- a/cmd/operator.go +++ b/cmd/operator.go @@ -14,6 +14,7 @@ import ( corev1alpha1 "github.com/platform-mesh/security-operator/api/v1alpha1" iclient "github.com/platform-mesh/security-operator/internal/client" "github.com/platform-mesh/security-operator/internal/controller" + "github.com/platform-mesh/security-operator/internal/predicates" internalwebhook "github.com/platform-mesh/security-operator/internal/webhook" "github.com/spf13/cobra" "google.golang.org/grpc" @@ -22,6 +23,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/webhook" mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" @@ -169,6 +171,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(ctx, log, fga, mgr). SetupWithManager(mgr, defaultCfg); err != nil { log.Error().Err(err).Str("controller", "store").Msg("unable to create controller") @@ -188,6 +198,16 @@ var operatorCmd = &cobra.Command{ log.Error().Err(err).Str("controller", "invite").Msg("unable to create controller") return err } + + if err = controller.NewOrgLogicalClusterReconciler(log, orgClient, operatorCfg, runtimeClient, mgr).SetupWithManager(mgr, defaultCfg, operatorCfg.InitializerName(), predicates.LogicalClusterIsAccountTypeOrg()); err != nil { + log.Error().Err(err).Str("controller", "logicalcluster").Msg("unable to create controller") + return err + } + + if err = controller.NewAccountLogicalClusterReconciler(log, operatorCfg, fga, mgr).SetupWithManager(mgr, defaultCfg, operatorCfg.InitializerName(), predicate.Not(predicates.LogicalClusterIsAccountTypeOrg())); err != nil { + log.Error().Err(err).Str("controller", "accounttypelogicalcluster").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/cmd/terminator.go b/cmd/terminator.go index 972fe58b..325af8b5 100644 --- a/cmd/terminator.go +++ b/cmd/terminator.go @@ -26,7 +26,6 @@ import ( "k8s.io/client-go/rest" "github.com/kcp-dev/logicalcluster/v3" - mcclient "github.com/kcp-dev/multicluster-provider/client" kcptenancyv1alphav1 "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" ) @@ -66,11 +65,6 @@ var terminatorCmd = &cobra.Command{ mgrOpts.LeaderElectionConfig = inClusterCfg } - mcc, err := mcclient.New(kcpCfg, client.Options{Scheme: scheme}) - if err != nil { - log.Error().Err(err).Msg("Failed to create multicluster client") - os.Exit(1) - } rootClient, err := iclient.NewForLogicalCluster(kcpCfg, scheme, logicalcluster.Name("root")) if err != nil { log.Error().Err(err).Msgf("Failed to get root client") @@ -112,7 +106,7 @@ var terminatorCmd = &cobra.Command{ defer func() { _ = conn.Close() }() fga := openfgav1.NewOpenFGAServiceClient(conn) - if err := controller.NewAccountLogicalClusterReconciler(log, terminatorCfg, fga, mcc, mgr). + if err := controller.NewAccountLogicalClusterInitializer(log, terminatorCfg, fga, mgr). SetupWithManager(mgr, defaultCfg, predicate.Not(predicates.LogicalClusterIsAccountTypeOrg())); err != nil { log.Error().Err(err).Msg("Unable to create AccountLogicalClusterTerminator") os.Exit(1) diff --git a/internal/controller/accountlogicalcluster_controller.go b/internal/controller/accountlogicalcluster_controller.go index f9641695..de7dd085 100644 --- a/internal/controller/accountlogicalcluster_controller.go +++ b/internal/controller/accountlogicalcluster_controller.go @@ -10,6 +10,7 @@ import ( 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/predicates" "github.com/platform-mesh/security-operator/internal/subroutine" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -17,36 +18,29 @@ import ( mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" mcreconcile "sigs.k8s.io/multicluster-runtime/pkg/reconcile" - mcclient "github.com/kcp-dev/multicluster-provider/client" kcpcorev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" ) -// AccountLogicalClusterReconciler acts as an initializer for account workspaces. -type AccountLogicalClusterReconciler struct { - log *logger.Logger - +type AccountTypeLogicalClusterReconciler struct { + log *logger.Logger mclifecycle *multicluster.LifecycleManager } -func NewAccountLogicalClusterReconciler(log *logger.Logger, cfg config.Config, fga openfgav1.OpenFGAServiceClient, mcc mcclient.ClusterClient, mgr mcmanager.Manager) *AccountLogicalClusterReconciler { - return &AccountLogicalClusterReconciler{ +func NewAccountLogicalClusterReconciler(log *logger.Logger, cfg config.Config, fga openfgav1.OpenFGAServiceClient, mgr mcmanager.Manager) *AccountTypeLogicalClusterReconciler { + return &AccountTypeLogicalClusterReconciler{ log: log, - mclifecycle: builder.NewBuilder("security", "AccountLogicalClusterReconciler", []lifecyclesubroutine.Subroutine{ - subroutine.NewAccountTuplesSubroutine(mcc, mgr, fga, cfg.FGA.CreatorRelation, cfg.FGA.ParentRelation, cfg.FGA.ObjectType), - }, log). - WithReadOnly(). - WithStaticThenExponentialRateLimiter(). - WithInitializer(cfg.InitializerName()). - WithTerminator(cfg.TerminatorName()). - BuildMultiCluster(mgr), + mclifecycle: builder.NewBuilder("logicalcluster", "AccountTypeLogicalClusterReconciler", []lifecyclesubroutine.Subroutine{ + subroutine.NewAccountTuplesSubroutine(mgr, fga, cfg.FGA.CreatorRelation, cfg.FGA.ParentRelation, cfg.FGA.ObjectType), + }, log).WithReadOnly().BuildMultiCluster(mgr), } } -func (r *AccountLogicalClusterReconciler) Reconcile(ctx context.Context, req mcreconcile.Request) (ctrl.Result, error) { +func (r *AccountTypeLogicalClusterReconciler) 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 *AccountLogicalClusterReconciler) SetupWithManager(mgr mcmanager.Manager, cfg *platformeshconfig.CommonServiceConfig, evp ...predicate.Predicate) error { - return r.mclifecycle.SetupWithManager(mgr, cfg.MaxConcurrentReconciles, "AccountLogicalCluster", &kcpcorev1alpha1.LogicalCluster{}, cfg.DebugLabelValue, r, r.log, evp...) +func (r *AccountTypeLogicalClusterReconciler) SetupWithManager(mgr mcmanager.Manager, cfg *platformeshconfig.CommonServiceConfig, initializerName string, evp ...predicate.Predicate) error { + allPredicates := append([]predicate.Predicate{predicates.HasInitializerPredicate(initializerName)}, evp...) + return r.mclifecycle.SetupWithManager(mgr, cfg.MaxConcurrentReconciles, "AccountTypeLogicalCluster", &kcpcorev1alpha1.LogicalCluster{}, cfg.DebugLabelValue, r, r.log, allPredicates...) } diff --git a/internal/controller/accountlogicalcluster_initializer_controller.go b/internal/controller/accountlogicalcluster_initializer_controller.go new file mode 100644 index 00000000..6d3e8655 --- /dev/null +++ b/internal/controller/accountlogicalcluster_initializer_controller.go @@ -0,0 +1,51 @@ +package controller + +import ( + "context" + + openfgav1 "github.com/openfga/api/proto/openfga/v1" + 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/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" +) + +// AccountLogicalClusterInitializer acts as an initializer for account workspaces. +type AccountLogicalClusterInitializer struct { + log *logger.Logger + + mclifecycle *multicluster.LifecycleManager +} + +func NewAccountLogicalClusterInitializer(log *logger.Logger, cfg config.Config, fga openfgav1.OpenFGAServiceClient, mgr mcmanager.Manager) *AccountLogicalClusterInitializer { + return &AccountLogicalClusterInitializer{ + log: log, + mclifecycle: builder.NewBuilder("security", "AccountLogicalClusterInitializer", []lifecyclesubroutine.Subroutine{ + subroutine.NewAccountTuplesSubroutine(mgr, fga, cfg.FGA.CreatorRelation, cfg.FGA.ParentRelation, cfg.FGA.ObjectType), + }, log). + WithReadOnly(). + WithStaticThenExponentialRateLimiter(). + WithInitializer(cfg.InitializerName()). + WithTerminator(cfg.TerminatorName()). + BuildMultiCluster(mgr), + } +} + +func (r *AccountLogicalClusterInitializer) 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 *AccountLogicalClusterInitializer) SetupWithManager(mgr mcmanager.Manager, cfg *platformeshconfig.CommonServiceConfig, evp ...predicate.Predicate) error { + return r.mclifecycle.SetupWithManager(mgr, cfg.MaxConcurrentReconciles, "AccountLogicalClusterInitializer", &kcpcorev1alpha1.LogicalCluster{}, cfg.DebugLabelValue, r, r.log, evp...) +} diff --git a/internal/controller/apibinding_controller.go b/internal/controller/apibinding_controller.go index 7bfdb353..a0659f8b 100644 --- a/internal/controller/apibinding_controller.go +++ b/internal/controller/apibinding_controller.go @@ -2,7 +2,6 @@ package controller import ( "context" - "net/url" platformeshconfig "github.com/platform-mesh/golang-commons/config" "github.com/platform-mesh/golang-commons/controller/lifecycle/builder" @@ -13,70 +12,14 @@ import ( "github.com/platform-mesh/security-operator/internal/subroutine" "github.com/rs/zerolog/log" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" "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" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/rest" - - "github.com/kcp-dev/logicalcluster/v3" - kcpapisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" kcpapisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" ) -func GetAllClient(config *rest.Config, schema *runtime.Scheme) (client.Client, error) { - allCfg := rest.CopyConfig(config) - - platformMeshClient, err := client.New(allCfg, client.Options{ - Scheme: schema, - }) - if err != nil { - log.Error().Err(err).Msg("unable to create client from config") - return nil, err - } - - var apiExportEndpointSlice kcpapisv1alpha1.APIExportEndpointSlice - err = platformMeshClient.Get(context.Background(), types.NamespacedName{Name: "core.platform-mesh.io"}, &apiExportEndpointSlice) - if err != nil { - log.Error().Err(err).Msg("unable to get APIExportEndpointSlice") - return nil, err - } - - virtualWorkspaceUrl, err := url.Parse(apiExportEndpointSlice.Status.APIExportEndpoints[0].URL) - if err != nil { - log.Error().Err(err).Msg("unable to parse virtual workspace URL") - return nil, err - } - - parsed, err := url.Parse(allCfg.Host) - if err != nil { - log.Error().Err(err).Msg("unable to parse host from config") - return nil, err - } - - parsed.Path, err = url.JoinPath(virtualWorkspaceUrl.Path, "clusters", logicalcluster.Wildcard.String()) - if err != nil { - log.Error().Err(err).Msg("unable to join path") - return nil, err - } - - allCfg.Host = parsed.String() - - log.Info().Str("host", allCfg.Host).Msg("using host") - - allClient, err := client.New(allCfg, client.Options{ - Scheme: schema, - }) - if err != nil { - return nil, err - } - return allClient, nil -} - func NewAPIBindingReconciler(ctx context.Context, logger *logger.Logger, mcMgr mcmanager.Manager) *APIBindingReconciler { allclient, err := iclient.NewForAllPlatformMeshResources(ctx, mcMgr.GetLocalManager().GetConfig(), mcMgr.GetLocalManager().GetScheme()) if err != nil { diff --git a/internal/controller/orglogicalcluster_controller.go b/internal/controller/orglogicalcluster_controller.go index 6840f8c6..2daca674 100644 --- a/internal/controller/orglogicalcluster_controller.go +++ b/internal/controller/orglogicalcluster_controller.go @@ -9,6 +9,7 @@ import ( 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/predicates" "github.com/platform-mesh/security-operator/internal/subroutine" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -21,8 +22,8 @@ import ( ) type OrgLogicalClusterReconciler struct { - log *logger.Logger - + log *logger.Logger + mgr mcmanager.Manager mclifecycle *multicluster.LifecycleManager } @@ -44,10 +45,9 @@ func NewOrgLogicalClusterReconciler(log *logger.Logger, orgClient client.Client, return &OrgLogicalClusterReconciler{ log: log, - mclifecycle: builder.NewBuilder("logicalcluster", "OrgLogicalClusterReconciler", subroutines, log). + mgr: mgr, + mclifecycle: builder.NewBuilder("logicalcluster", "LogicalClusterReconciler", subroutines, log). WithReadOnly(). - WithStaticThenExponentialRateLimiter(). - WithInitializer(cfg.InitializerName()). BuildMultiCluster(mgr), } } @@ -57,6 +57,7 @@ func (r *OrgLogicalClusterReconciler) Reconcile(ctx context.Context, req mcrecon return r.mclifecycle.Reconcile(ctxWithCluster, req, &kcpcorev1alpha1.LogicalCluster{}) } -func (r *OrgLogicalClusterReconciler) 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...) +func (r *OrgLogicalClusterReconciler) SetupWithManager(mgr mcmanager.Manager, cfg *platformeshconfig.CommonServiceConfig, initializerName string, evp ...predicate.Predicate) error { + allPredicates := append([]predicate.Predicate{predicates.HasInitializerPredicate(initializerName)}, evp...) + return r.mclifecycle.SetupWithManager(mgr, cfg.MaxConcurrentReconciles, "LogicalCluster", &kcpcorev1alpha1.LogicalCluster{}, cfg.DebugLabelValue, r, r.log, allPredicates...) } diff --git a/internal/controller/orglogicalcluster_initializer_controller.go b/internal/controller/orglogicalcluster_initializer_controller.go new file mode 100644 index 00000000..5b77d3c8 --- /dev/null +++ b/internal/controller/orglogicalcluster_initializer_controller.go @@ -0,0 +1,62 @@ +package controller + +import ( + "context" + + 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/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 OrgLogicalClusterInitializer struct { + log *logger.Logger + + mclifecycle *multicluster.LifecycleManager +} + +func NewOrgLogicalClusterInitializer(log *logger.Logger, orgClient client.Client, cfg config.Config, inClusterClient client.Client, mgr mcmanager.Manager) *OrgLogicalClusterInitializer { + var subroutines []lifecyclesubroutine.Subroutine + + if cfg.Initializer.WorkspaceInitializerEnabled { + subroutines = append(subroutines, subroutine.NewWorkspaceInitializer(orgClient, cfg, mgr, cfg.FGA.CreatorRelation, cfg.FGA.ObjectType)) + } + if cfg.Initializer.IDPEnabled { + subroutines = append(subroutines, subroutine.NewIDPSubroutine(orgClient, mgr, cfg)) + } + if cfg.Initializer.InviteEnabled { + subroutines = append(subroutines, subroutine.NewInviteSubroutine(orgClient, mgr)) + } + if cfg.Initializer.WorkspaceAuthEnabled { + subroutines = append(subroutines, subroutine.NewWorkspaceAuthConfigurationSubroutine(orgClient, inClusterClient, mgr, cfg)) + } + + return &OrgLogicalClusterInitializer{ + log: log, + mclifecycle: builder.NewBuilder("logicalcluster", "OrgLogicalClusterInitializer", subroutines, log). + WithReadOnly(). + WithStaticThenExponentialRateLimiter(). + WithInitializer(cfg.InitializerName()). + BuildMultiCluster(mgr), + } +} + +func (r *OrgLogicalClusterInitializer) 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 *OrgLogicalClusterInitializer) 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/predicates/initializer.go b/internal/predicates/initializer.go new file mode 100644 index 00000000..11b3192b --- /dev/null +++ b/internal/predicates/initializer.go @@ -0,0 +1,26 @@ +package predicates + +import ( + "fmt" + "slices" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + kcpcorev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" +) + +func HasInitializerPredicate(initializerName string) predicate.Predicate { + initializer := kcpcorev1alpha1.LogicalClusterInitializer(initializerName) + return predicate.NewPredicateFuncs(func(object client.Object) bool { + lc, ok := object.(*kcpcorev1alpha1.LogicalCluster) + if !ok { + panic(fmt.Errorf("received non-LogicalCluster resource in HasInitializer predicate")) + } + 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/account_tuples.go b/internal/subroutine/account_tuples.go index dcf3bf5d..b3feac53 100644 --- a/internal/subroutine/account_tuples.go +++ b/internal/subroutine/account_tuples.go @@ -23,7 +23,6 @@ import ( kerrors "k8s.io/apimachinery/pkg/api/errors" "github.com/kcp-dev/logicalcluster/v3" - mcclient "github.com/kcp-dev/multicluster-provider/client" kcpcore "github.com/kcp-dev/sdk/apis/core" kcpcorev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" ) @@ -34,7 +33,6 @@ const accountTuplesTerminatorFinalizer = "core.platform-mesh.io/account-tuples-t // "org"-type when initializing, and deletes them when terminating. type AccountTuplesSubroutine struct { mgr mcmanager.Manager - mcc mcclient.ClusterClient fga openfgav1.OpenFGAServiceClient objectType string @@ -51,23 +49,18 @@ func (s *AccountTuplesSubroutine) Process(ctx context.Context, instance runtimeo // Initialize implements lifecycle.Initializer. func (s *AccountTuplesSubroutine) Initialize(ctx context.Context, instance runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { lc := instance.(*kcpcorev1alpha1.LogicalCluster) - acc, ai, opErr := AccountAndInfoForLogicalCluster(ctx, s.mgr, lc) + acc, ai, opErr := accountAndInfoForLogicalCluster(ctx, s.mgr, lc) if opErr != nil { return ctrl.Result{}, opErr } if updated := controllerutil.AddFinalizer(&ai, accountTuplesTerminatorFinalizer); updated { - lcID, ok := mccontext.ClusterFrom(ctx) - if !ok { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("cluster name not found in context"), true, true) - } - - lcClient, err := iclient.NewForLogicalCluster(s.mgr.GetLocalManager().GetConfig(), s.mgr.GetLocalManager().GetScheme(), logicalcluster.Name(lcID)) + cl, err := s.mgr.ClusterFromContext(ctx) if err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("getting client: %w", err), true, true) + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to get cluster from context %w", err), true, true) } - if err := lcClient.Update(ctx, &ai); err != nil { + if err := cl.GetClient().Update(ctx, &ai); err != nil { return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("updating AccountInfo to set finalizer: %w", err), true, true) } } @@ -87,7 +80,7 @@ func (s *AccountTuplesSubroutine) Initialize(ctx context.Context, instance runti // Terminate implements lifecycle.Terminator. func (s *AccountTuplesSubroutine) Terminate(ctx context.Context, instance runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { lc := instance.(*kcpcorev1alpha1.LogicalCluster) - _, ai, opErr := AccountAndInfoForLogicalCluster(ctx, s.mgr, lc) + _, ai, opErr := accountAndInfoForLogicalCluster(ctx, s.mgr, lc) if opErr != nil { return ctrl.Result{}, opErr } @@ -152,10 +145,9 @@ func (s *AccountTuplesSubroutine) Finalizers(_ runtimeobject.RuntimeObject) []st // GetName implements lifecycle.Subroutine. func (s *AccountTuplesSubroutine) GetName() string { return "AccountTuplesSubroutine" } -func NewAccountTuplesSubroutine(mcc mcclient.ClusterClient, mgr mcmanager.Manager, fga openfgav1.OpenFGAServiceClient, creatorRelation, parentRelation, objectType string) *AccountTuplesSubroutine { +func NewAccountTuplesSubroutine(mgr mcmanager.Manager, fga openfgav1.OpenFGAServiceClient, creatorRelation, parentRelation, objectType string) *AccountTuplesSubroutine { return &AccountTuplesSubroutine{ mgr: mgr, - mcc: mcc, fga: fga, creatorRelation: creatorRelation, parentRelation: parentRelation, @@ -172,7 +164,7 @@ var ( // AccountAndInfoForLogicalCluster fetches the AccountInfo from the // LogicalCluster and the corresponding Account from the parent account's // workspace. -func AccountAndInfoForLogicalCluster(ctx context.Context, mgr mcmanager.Manager, lc *kcpcorev1alpha1.LogicalCluster) (accountsv1alpha1.Account, accountsv1alpha1.AccountInfo, errors.OperatorError) { +func accountAndInfoForLogicalCluster(ctx context.Context, mgr mcmanager.Manager, lc *kcpcorev1alpha1.LogicalCluster) (accountsv1alpha1.Account, accountsv1alpha1.AccountInfo, errors.OperatorError) { if lc.Annotations[kcpcore.LogicalClusterPathAnnotationKey] == "" { return accountsv1alpha1.Account{}, accountsv1alpha1.AccountInfo{}, errors.NewOperatorError(fmt.Errorf("annotation on LogicalCluster is not set"), true, true) } @@ -187,6 +179,9 @@ func AccountAndInfoForLogicalCluster(ctx context.Context, mgr mcmanager.Manager, if err != nil { return accountsv1alpha1.Account{}, accountsv1alpha1.AccountInfo{}, errors.NewOperatorError(fmt.Errorf("getting client: %w", err), true, true) } + + // The AccountInfo in the logical cluster belongs to the Account the + // Workspace was created for var ai accountsv1alpha1.AccountInfo if err := lcClient.Get(ctx, client.ObjectKey{ Name: "account", diff --git a/internal/subroutine/idp.go b/internal/subroutine/idp.go index 6ffae6fc..15d2c987 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" @@ -104,34 +103,28 @@ func (i *IDPSubroutine) Initialize(ctx context.Context, instance runtimeobject.R 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 { - idp.Spec.RegistrationAllowed = i.registrationAllowed - - for _, desired := range clients { - idp.Spec.Clients = ensureClient(idp.Spec.Clients, desired) } return nil }) @@ -209,24 +202,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, ":") diff --git a/internal/subroutine/workspace_initializer.go b/internal/subroutine/workspace_initializer.go index dba9b64c..10cead48 100644 --- a/internal/subroutine/workspace_initializer.go +++ b/internal/subroutine/workspace_initializer.go @@ -12,20 +12,16 @@ import ( lifecyclesubroutine "github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine" "github.com/platform-mesh/golang-commons/errors" "github.com/platform-mesh/security-operator/api/v1alpha1" - iclient "github.com/platform-mesh/security-operator/internal/client" "github.com/platform-mesh/security-operator/internal/config" "github.com/platform-mesh/security-operator/pkg/fga" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - mccontext "sigs.k8s.io/multicluster-runtime/pkg/context" mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/kcp-dev/logicalcluster/v3" - kcpcore "github.com/kcp-dev/sdk/apis/core" kcpcorev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" ) @@ -83,19 +79,13 @@ func (w *workspaceInitializer) Process(ctx context.Context, instance runtimeobje // Initialize implements lifecycle.Initializer. func (w *workspaceInitializer) Initialize(ctx context.Context, instance runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { lc := instance.(*kcpcorev1alpha1.LogicalCluster) - p := lc.Annotations[kcpcore.LogicalClusterPathAnnotationKey] - if p == "" { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("annotation on LogicalCluster is not set"), true, true) - } - lcID, _ := mccontext.ClusterFrom(ctx) - - lcClient, err := iclient.NewForLogicalCluster(w.mgr.GetLocalManager().GetConfig(), w.mgr.GetLocalManager().GetScheme(), logicalcluster.Name(lcID)) + cluster, err := w.mgr.ClusterFromContext(ctx) if err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("getting client: %w", err), true, true) + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("getting cluster from context: %w", err), true, true) } var ai accountsv1alpha1.AccountInfo - if err := lcClient.Get(ctx, client.ObjectKey{ + if err := cluster.GetClient().Get(ctx, client.ObjectKey{ Name: "account", }, &ai); err != nil && !kerrors.IsNotFound(err) { return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("getting AccountInfo for LogicalCluster: %w", err), true, true) @@ -103,16 +93,11 @@ func (w *workspaceInitializer) Initialize(ctx context.Context, instance runtimeo return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("AccountInfo not found yet, requeueing"), true, false) } - orgsClient, err := iclient.NewForLogicalCluster(w.mgr.GetLocalManager().GetConfig(), w.mgr.GetLocalManager().GetScheme(), logicalcluster.Name("root:orgs")) - if err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("getting parent organisation client: %w", err), true, true) - } - var acc accountsv1alpha1.Account - if err := orgsClient.Get(ctx, client.ObjectKey{ + if err := w.orgsClient.Get(ctx, client.ObjectKey{ Name: ai.Spec.Account.Name, }, &acc); err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("getting Account in platform-mesh-system: %w", err), true, true) + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("getting Account in orgs workspace: %w", err), true, true) } store := v1alpha1.Store{ @@ -162,11 +147,6 @@ func (w *workspaceInitializer) Initialize(ctx context.Context, instance runtimeo return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("store id is empty"), true, false) } - cluster, err := w.mgr.ClusterFromContext(ctx) - if err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("unable to get cluster from context: %w", err), true, false) - } - accountInfo := accountsv1alpha1.AccountInfo{ ObjectMeta: metav1.ObjectMeta{Name: "account"}, }