diff --git a/.mockery.yaml b/.mockery.yaml index 1f1f902..d628b37 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -13,3 +13,17 @@ packages: outpkg: mocks interfaces: Client: + + sigs.k8s.io/multicluster-runtime/pkg/manager: + config: + dir: internal/subroutine/mocks + outpkg: mocks + interfaces: + Manager: + + sigs.k8s.io/controller-runtime/pkg/cluster: + config: + dir: internal/subroutine/mocks + outpkg: mocks + interfaces: + Cluster: \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 04c74f8..bd3ab1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.24.6-bullseye AS builder +FROM golang:1.25.2-bookworm AS builder ARG TARGETOS ARG TARGETARCH diff --git a/cmd/initializer.go b/cmd/initializer.go index 3b8719e..9896c23 100644 --- a/cmd/initializer.go +++ b/cmd/initializer.go @@ -6,7 +6,10 @@ import ( helmv2 "github.com/fluxcd/helm-controller/api/v2" sourcev1 "github.com/fluxcd/source-controller/api/v1" + "github.com/kcp-dev/logicalcluster/v3" + "github.com/kcp-dev/multicluster-provider/initializingworkspaces" + pmcontext "github.com/platform-mesh/golang-commons/context" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -14,16 +17,18 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/kcp" "sigs.k8s.io/controller-runtime/pkg/metrics/server" "github.com/platform-mesh/security-operator/internal/controller" + mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" ) var initializerCmd = &cobra.Command{ Use: "initializer", Short: "FGA initializer for the organization workspacetype", RunE: func(cmd *cobra.Command, args []string) error { + ctx, _, shutdown := pmcontext.StartContext(log, appCfg, defaultCfg.ShutdownTimeout) + defer shutdown() mgrCfg := ctrl.GetConfigOrDie() @@ -50,7 +55,17 @@ var initializerCmd = &cobra.Command{ } mgrOpts.LeaderElectionConfig = inClusterCfg } - mgr, err := kcp.NewClusterAwareManager(mgrCfg, mgrOpts) + + provider, err := initializingworkspaces.New(mgrCfg, initializingworkspaces.Options{ + InitializerName: appCfg.InitializerName, + Scheme: mgrOpts.Scheme, + }) + if err != nil { + log.Error().Err(err).Msg("unable to construct cluster provider") + os.Exit(1) + } + + mgr, err := mcmanager.New(mgrCfg, provider, mgrOpts) if err != nil { setupLog.Error(err, "Failed to create manager") os.Exit(1) @@ -60,7 +75,7 @@ var initializerCmd = &cobra.Command{ utilruntime.Must(sourcev1.AddToScheme(runtimeScheme)) utilruntime.Must(helmv2.AddToScheme(runtimeScheme)) - orgClient, err := logicalClusterClientFromKey(mgr, log)(logicalcluster.Name("root:orgs")) + orgClient, err := logicalClusterClientFromKey(mgr.GetLocalManager(), log)(logicalcluster.Name("root:orgs")) if err != nil { setupLog.Error(err, "Failed to create org client") os.Exit(1) @@ -78,7 +93,12 @@ var initializerCmd = &cobra.Command{ os.Exit(1) } - if err := controller.NewLogicalClusterReconciler(log, mgrCfg, mgr.GetClient(), orgClient, appCfg, inClusterClient).SetupWithManager(mgr, defaultCfg, log); err != nil { + if appCfg.IDP.AdditionalRedirectURLs == nil { + appCfg.IDP.AdditionalRedirectURLs = []string{} + } + + if err := controller.NewLogicalClusterReconciler(log, orgClient, appCfg, inClusterClient, mgr). + SetupWithManager(mgr, defaultCfg); err != nil { setupLog.Error(err, "unable to create controller", "controller", "LogicalCluster") os.Exit(1) } @@ -92,6 +112,12 @@ var initializerCmd = &cobra.Command{ os.Exit(1) } + go func() { + if err := provider.Run(ctx, mgr); err != nil { + log.Fatal().Err(err).Msg("unable to run provider") + } + }() + setupLog.Info("starting manager") return mgr.Start(ctrl.SetupSignalHandler()) diff --git a/cmd/model_generator.go b/cmd/model_generator.go index 27c2df8..fa7f143 100644 --- a/cmd/model_generator.go +++ b/cmd/model_generator.go @@ -3,7 +3,9 @@ package cmd import ( "context" "crypto/tls" + "fmt" + "github.com/kcp-dev/multicluster-provider/apiexport" platformeshcontext "github.com/platform-mesh/golang-commons/context" appsv1 "k8s.io/api/apps/v1" @@ -13,9 +15,9 @@ import ( "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/kcp" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/metrics/server" + mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" securityv1alpha1 "github.com/platform-mesh/security-operator/api/v1alpha1" "github.com/platform-mesh/security-operator/internal/controller" @@ -56,18 +58,31 @@ var modelGeneratorCmd = &cobra.Command{ } mgrOpts.LeaderElectionConfig = inClusterCfg } + runtimeScheme := runtime.NewScheme() + utilruntime.Must(appsv1.AddToScheme(runtimeScheme)) + utilruntime.Must(securityv1alpha1.AddToScheme(runtimeScheme)) - mgr, err := kcp.NewClusterAwareManager(cfg, mgrOpts) + if mgrOpts.Scheme == nil { + log.Error().Err(fmt.Errorf("scheme should not be nil")).Msg("scheme should not be nil") + return fmt.Errorf("scheme should not be nil") + } + + provider, err := apiexport.New(cfg, apiexport.Options{ + Scheme: mgrOpts.Scheme, + }) if err != nil { - setupLog.Error(err, "unable to setup manager") + log.Error().Err(err).Msg("Failed to create apiexport provider") + return err } - runtimeScheme := runtime.NewScheme() - utilruntime.Must(appsv1.AddToScheme(runtimeScheme)) - utilruntime.Must(securityv1alpha1.AddToScheme(runtimeScheme)) + mgr, err := mcmanager.New(cfg, provider, mgrOpts) + if err != nil { + log.Error().Err(err).Msg("Failed to create manager") + return err + } - if err := controller.NewAPIBindingReconciler(mgr.GetClient(), log, logicalClusterClientFromKey(mgr, log)). - SetupWithManager(mgr, log, defaultCfg); err != nil { + if err := controller.NewAPIBindingReconciler(log, mgr). + SetupWithManager(mgr, defaultCfg); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Resource") return err } @@ -81,6 +96,12 @@ var modelGeneratorCmd = &cobra.Command{ return err } + go func() { + if err := provider.Run(ctx, mgr); err != nil { + log.Fatal().Err(err).Msg("unable to run provider") + } + }() + setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager") diff --git a/cmd/operator.go b/cmd/operator.go index dc0dc6c..b46dee8 100644 --- a/cmd/operator.go +++ b/cmd/operator.go @@ -11,6 +11,7 @@ import ( apisv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1" kcpcorev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1" "github.com/kcp-dev/logicalcluster/v3" + "github.com/kcp-dev/multicluster-provider/apiexport" accountsv1alpha1 "github.com/platform-mesh/account-operator/api/v1alpha1" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -23,8 +24,9 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/kcp" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" openfgav1 "github.com/openfga/api/proto/openfga/v1" platformeshcontext "github.com/platform-mesh/golang-commons/context" @@ -35,7 +37,6 @@ import ( kcptenancyv1alphav1 "github.com/kcp-dev/kcp/sdk/apis/tenancy/v1alpha1" corev1alpha1 "github.com/platform-mesh/security-operator/api/v1alpha1" "github.com/platform-mesh/security-operator/internal/controller" - "github.com/platform-mesh/security-operator/internal/subroutine" // +kubebuilder:scaffold:imports ) @@ -43,7 +44,9 @@ var ( scheme = runtime.NewScheme() ) -func logicalClusterClientFromKey(mgr ctrl.Manager, log *logger.Logger) subroutine.NewLogicalClusterClientFunc { +type NewLogicalClusterClientFunc func(clusterKey logicalcluster.Name) (client.Client, error) + +func logicalClusterClientFromKey(mgr ctrl.Manager, log *logger.Logger) NewLogicalClusterClientFunc { return func(clusterKey logicalcluster.Name) (client.Client, error) { cfg := rest.CopyConfig(mgr.GetConfig()) @@ -55,6 +58,8 @@ func logicalClusterClientFromKey(mgr ctrl.Manager, log *logger.Logger) subroutin parsed.Path = fmt.Sprintf("/clusters/%s", clusterKey) + log.Info().Msg(fmt.Sprintf("HOST from logical cluster client from key -- %s", parsed.String())) + cfg.Host = parsed.String() return client.New(cfg, client.Options{ @@ -110,9 +115,22 @@ var operatorCmd = &cobra.Command{ mgrOpts.LeaderElectionConfig = inClusterCfg } - mgr, err := kcp.NewClusterAwareManager(cfg, mgrOpts) + if mgrOpts.Scheme == nil { + log.Error().Err(fmt.Errorf("scheme should not be nil")).Msg("scheme should not be nil") + return fmt.Errorf("scheme should not be nil") + } + + provider, err := apiexport.New(cfg, apiexport.Options{ + Scheme: mgrOpts.Scheme, + }) + if err != nil { + setupLog.Error(err, "unable to construct cluster provider") + return err + } + + mgr, err := mcmanager.New(cfg, provider, mgrOpts) if err != nil { - log.Error().Err(err).Msg("unable to start manager") + setupLog.Error(err, "Failed to create manager") return err } @@ -124,14 +142,14 @@ var operatorCmd = &cobra.Command{ fga := openfgav1.NewOpenFGAServiceClient(conn) - if err = controller.NewStoreReconciler(log, mgr.GetClient(), fga, logicalClusterClientFromKey(mgr, log)). - SetupWithManager(mgr, defaultCfg, log); err != nil { + if err = controller.NewStoreReconciler(log, fga, mgr). + SetupWithManager(mgr, defaultCfg); err != nil { log.Error().Err(err).Str("controller", "store").Msg("unable to create controller") return err } if err = controller. - NewAuthorizationModelReconciler(log, mgr.GetClient(), fga, logicalClusterClientFromKey(mgr, log)). - SetupWithManager(mgr, defaultCfg, log); err != nil { + NewAuthorizationModelReconciler(log, fga, mgr). + SetupWithManager(mgr, defaultCfg); err != nil { log.Error().Err(err).Str("controller", "authorizationmodel").Msg("unable to create controller") return err } @@ -146,6 +164,12 @@ var operatorCmd = &cobra.Command{ return err } + go func() { + if err := provider.Run(ctx, mgr); err != nil { + log.Fatal().Err(err).Msg("unable to run provider") + } + }() + setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { log.Error().Err(err).Msg("problem running manager") diff --git a/go.mod b/go.mod index a1ea5b6..bae0cf4 100644 --- a/go.mod +++ b/go.mod @@ -1,60 +1,27 @@ module github.com/platform-mesh/security-operator -go 1.24.5 - -replace sigs.k8s.io/controller-runtime => github.com/kcp-dev/controller-runtime v0.19.0-kcp.1 - -replace ( - k8s.io/api => github.com/kcp-dev/kubernetes/staging/src/k8s.io/api v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/apimachinery => github.com/kcp-dev/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/apiserver => github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/client-go => github.com/kcp-dev/kubernetes/staging/src/k8s.io/client-go v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/cloud-provider => github.com/kcp-dev/kubernetes/staging/src/k8s.io/cloud-provider v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/cluster-bootstrap => github.com/kcp-dev/kubernetes/staging/src/k8s.io/cluster-bootstrap v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/code-generator => github.com/kcp-dev/kubernetes/staging/src/k8s.io/code-generator v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/component-base => github.com/kcp-dev/kubernetes/staging/src/k8s.io/component-base v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/component-helpers => github.com/kcp-dev/kubernetes/staging/src/k8s.io/component-helpers v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/controller-manager => github.com/kcp-dev/kubernetes/staging/src/k8s.io/controller-manager v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/cri-api => github.com/kcp-dev/kubernetes/staging/src/k8s.io/cri-api v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/cri-client => github.com/kcp-dev/kubernetes/staging/src/k8s.io/cri-client v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/csi-translation-lib => github.com/kcp-dev/kubernetes/staging/src/k8s.io/csi-translation-lib v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/dynamic-resource-allocation => github.com/kcp-dev/kubernetes/staging/src/k8s.io/dynamic-resource-allocation v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/endpointslice => github.com/kcp-dev/kubernetes/staging/src/k8s.io/endpointslice v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/externaljwt => github.com/kcp-dev/kubernetes/staging/src/k8s.io/externaljwt v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/kms => github.com/kcp-dev/kubernetes/staging/src/k8s.io/kms v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/kube-aggregator => github.com/kcp-dev/kubernetes/staging/src/k8s.io/kube-aggregator v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/kube-controller-manager => github.com/kcp-dev/kubernetes/staging/src/k8s.io/kube-controller-manager v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/kube-proxy => github.com/kcp-dev/kubernetes/staging/src/k8s.io/kube-proxy v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/kube-scheduler => github.com/kcp-dev/kubernetes/staging/src/k8s.io/kube-scheduler v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/kubectl => github.com/kcp-dev/kubernetes/staging/src/k8s.io/kubectl v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/kubelet => github.com/kcp-dev/kubernetes/staging/src/k8s.io/kubelet v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/kubernetes => github.com/kcp-dev/kubernetes v1.32.3 - k8s.io/metrics => github.com/kcp-dev/kubernetes/staging/src/k8s.io/metrics v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/mount-utils => github.com/kcp-dev/kubernetes/staging/src/k8s.io/mount-utils v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/pod-security-admission => github.com/kcp-dev/kubernetes/staging/src/k8s.io/pod-security-admission v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/sample-apiserver => github.com/kcp-dev/kubernetes/staging/src/k8s.io/sample-apiserver v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/sample-cli-plugin => github.com/kcp-dev/kubernetes/staging/src/k8s.io/sample-cli-plugin v0.0.0-20250816165010-ffe1d7c8649b - k8s.io/sample-controller => github.com/kcp-dev/kubernetes/staging/src/k8s.io/sample-controller v0.0.0-20250816165010-ffe1d7c8649b -) +go 1.25.2 require ( github.com/go-logr/logr v1.4.3 github.com/kcp-dev/kcp/sdk v0.28.1-0.20250915073746-2b42b96efc54 github.com/kcp-dev/logicalcluster/v3 v3.0.5 + github.com/kcp-dev/multicluster-provider v0.0.0-20250827085327-2b5ca378b7b4 github.com/openfga/api/proto v0.0.0-20250909173124-0ac19aac54f2 - github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250428093642-7aeebe78bbfe + github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250919191407-efa08b02a76a github.com/platform-mesh/account-operator v0.3.1 - github.com/platform-mesh/golang-commons v0.1.32 + github.com/platform-mesh/golang-commons v0.6.0 github.com/spf13/cobra v1.10.1 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 google.golang.org/grpc v1.76.0 google.golang.org/protobuf v1.36.10 - k8s.io/api v0.33.3 - k8s.io/apiextensions-apiserver v0.33.3 - k8s.io/apimachinery v0.33.3 - k8s.io/client-go v0.33.3 + k8s.io/api v0.34.1 + k8s.io/apiextensions-apiserver v0.34.1 + k8s.io/apimachinery v0.34.1 + k8s.io/client-go v0.34.1 sigs.k8s.io/controller-runtime v0.22.2 + sigs.k8s.io/multicluster-runtime v0.21.0-alpha.8 ) require ( @@ -62,25 +29,29 @@ require ( github.com/fluxcd/pkg/apis/acl v0.7.0 // indirect github.com/fluxcd/pkg/apis/kustomize v1.10.0 // indirect github.com/fluxcd/pkg/apis/meta v1.12.0 // indirect + github.com/google/btree v1.1.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/sync v0.17.0 // indirect + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect ) require ( - github.com/99designs/gqlgen v0.17.78 // indirect + github.com/99designs/gqlgen v0.17.81 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fluxcd/helm-controller/api v1.3.0 github.com/fluxcd/source-controller/api v1.6.2 github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.8.0 // indirect - github.com/getsentry/sentry-go v0.35.2 // indirect - github.com/go-jose/go-jose/v4 v4.1.2 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/getsentry/sentry-go v0.35.3 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zerologr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect @@ -88,7 +59,7 @@ require ( github.com/go-openapi/swag v0.23.1 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect @@ -99,21 +70,21 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250728122101-adbf20db3e51 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.0 // indirect; indir k8s.io/api v0.34.0 github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/onsi/gomega v1.36.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.23.0 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect - github.com/rs/zerolog v1.34.0 // indirect + github.com/rs/zerolog v1.34.0 github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sosodev/duration v1.3.1 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect @@ -134,13 +105,12 @@ require ( go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.8.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.41.0 // indirect - golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect - golang.org/x/net v0.43.0 // indirect - golang.org/x/oauth2 v0.31.0 // indirect + golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/sys v0.36.0 // indirect - golang.org/x/term v0.34.0 // indirect - golang.org/x/text v0.28.0 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect golang.org/x/time v0.11.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect @@ -149,10 +119,8 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index 601317c..0c084d1 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= -github.com/99designs/gqlgen v0.17.78 h1:bhIi7ynrc3js2O8wu1sMQj1YHPENDt3jQGyifoBvoVI= -github.com/99designs/gqlgen v0.17.78/go.mod h1:yI/o31IauG2kX0IsskM4R894OCCG1jXJORhtLQqB7Oc= +github.com/99designs/gqlgen v0.17.81 h1:kCkN/xVyRb5rEQpuwOHRTYq83i0IuTQg9vdIiwEerTs= +github.com/99designs/gqlgen v0.17.81/go.mod h1:vgNcZlLwemsUhYim4dC1pvFP5FX0pr2Y+uYUoHFb1ig= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= @@ -20,8 +20,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= -github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= @@ -42,14 +42,14 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= -github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/getsentry/sentry-go v0.35.2 h1:jKuujpRwa8FFRYMIwwZpu83Xh0voll9bmvyc6310WBM= -github.com/getsentry/sentry-go v0.35.2/go.mod h1:mdL49ixwT2yi57k5eh7mpnDyPybixPzlzEJFu0Z76QA= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/getsentry/sentry-go v0.35.3 h1:u5IJaEqZyPdWqe/hKlBKBBnMTSxB/HenCqF3QLabeds= +github.com/getsentry/sentry-go v0.35.3/go.mod h1:mdL49ixwT2yi57k5eh7mpnDyPybixPzlzEJFu0Z76QA= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= -github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -76,11 +76,12 @@ github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9v github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= -github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= +github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -107,22 +108,12 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250728122101-adbf20db3e51 h1:l38RDS+VUMx9etvyaCgJIZa4nM7FaNevNubWN0kDZY4= github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250728122101-adbf20db3e51/go.mod h1:rF1jfvUfPjFXs+HV/LN1BtPzAz1bfjJOwVa+hAVfroQ= -github.com/kcp-dev/controller-runtime v0.19.0-kcp.1 h1:mbCyVzWuJpg+pkzIkIKLltiOgOSiQ3bqWmHi2mftzgc= -github.com/kcp-dev/controller-runtime v0.19.0-kcp.1/go.mod h1:jwK5sBnpu/xJJ+xdpSzzI0aM52E/EvF0uLF9bR61h/Y= github.com/kcp-dev/kcp/sdk v0.28.1-0.20250915073746-2b42b96efc54 h1:CUfnrqLukdY9a1KbZs0Lh0lzxeWOex5oZ+yEdHziNEs= github.com/kcp-dev/kcp/sdk v0.28.1-0.20250915073746-2b42b96efc54/go.mod h1:aC2BPGPvy8QtkI2gQNH9NfW6xpfGIKZkR93gy9O02BE= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/api v0.0.0-20250816165010-ffe1d7c8649b h1:CyQuxPfhWg8KdwfmY5aE6KABsh/QhkDXTH2msezxCFY= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/api v0.0.0-20250816165010-ffe1d7c8649b/go.mod h1:uiagPCm7MlCfQpIm2xwPTRf8727wbCZCMgHI9uHcMpg= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20250816165010-ffe1d7c8649b h1:cJfP+GeRW8O1/49MKUoJ6xbiFiMMOEr/DSrt2y9r0hc= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20250816165010-ffe1d7c8649b/go.mod h1:6XMZJoNYwuMArBvS2acFkTR1KqyHSp2QXRLRx9eTk5w= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20250816165010-ffe1d7c8649b h1:C21pLvKT2MUE38+ZNDXeucEbRdb7rewRpBp4C5lzz6M= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20250816165010-ffe1d7c8649b/go.mod h1:STCgTiD+xCCHsfLOPHn5sNVsyktakX/ctW3dMv3erh0= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/client-go v0.0.0-20250816165010-ffe1d7c8649b h1:kEieYK/XCUycPf5DCEUZNPvDVHr4ao+rxZvdOQXlMQk= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/client-go v0.0.0-20250816165010-ffe1d7c8649b/go.mod h1:omt22adyHpxAelVTfG1bssg+xoAUc+Cg+0CXn0Oaim0= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/component-base v0.0.0-20250816165010-ffe1d7c8649b h1:OazHpbyl1+WvViAUEZw2PxMZNrd5LOPDD+bhnfL5cQM= -github.com/kcp-dev/kubernetes/staging/src/k8s.io/component-base v0.0.0-20250816165010-ffe1d7c8649b/go.mod h1:Z+AmCbP/esJzSqF5Otj149NR+8fqJHWBgokGrRp0a1c= github.com/kcp-dev/logicalcluster/v3 v3.0.5 h1:JbYakokb+5Uinz09oTXomSUJVQsqfxEvU4RyHUYxHOU= github.com/kcp-dev/logicalcluster/v3 v3.0.5/go.mod h1:EWBUBxdr49fUB1cLMO4nOdBWmYifLbP1LfoL20KkXYY= +github.com/kcp-dev/multicluster-provider v0.0.0-20250827085327-2b5ca378b7b4 h1:GUihV22j2J/cGF5Svr/zGVvfqTJepIO+sOnYTn9o4Vc= +github.com/kcp-dev/multicluster-provider v0.0.0-20250827085327-2b5ca378b7b4/go.mod h1:E/NxN2SMtC7b6iXgFMlQYWA7lJIfDPqRkPdvpxOEQLA= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -145,8 +136,9 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM= @@ -155,8 +147,8 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/openfga/api/proto v0.0.0-20250909173124-0ac19aac54f2 h1:C0ZsNwvnOos+K2bbMDmX2++DDzjCGd8dLa8ZZaGB5ys= github.com/openfga/api/proto v0.0.0-20250909173124-0ac19aac54f2/go.mod h1:XDX4qYNBUM2Rsa2AbKPh+oocZc2zgme+EF2fFC6amVU= -github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250428093642-7aeebe78bbfe h1:X1g0rBUMvvzMudsak/jmoEZ1NhSsp6yR0VGxWHnGMzs= -github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250428093642-7aeebe78bbfe/go.mod h1:5Z0pbTT7Jz/oQFLfadb+C5t5NwHrduAO7j7L07Ec1GM= +github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250919191407-efa08b02a76a h1:mwpFZZdH4B6hHDGuQjkvebkp8+eLEi63nZ8XniGTyvo= +github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250919191407-efa08b02a76a/go.mod h1:BG26d1Fk4GSg0wMj60TRJ6Pe4ka2WQ33akhO+mzt3t0= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= @@ -165,17 +157,17 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/platform-mesh/account-operator v0.3.1 h1:i+QBX3vauHEU+DBg0rSbJCQSKWYa2eSz9qWt1Fj9UAY= github.com/platform-mesh/account-operator v0.3.1/go.mod h1:ytszzXct5SiUdEdcbE5wvtZca0ZiJraflFmw7KGhQY8= -github.com/platform-mesh/golang-commons v0.1.32 h1:bNjuLApJzJWcxtpGkd5+uF8uSA5q+u1XNs6gAYCmkFM= -github.com/platform-mesh/golang-commons v0.1.32/go.mod h1:udKDsBJrdnbzkL5qN2zt63dDVqKQg8tEe63t1G0qT+w= +github.com/platform-mesh/golang-commons v0.6.0 h1:1LWkDOyGeeC6CHdPDfofzmiloqgnM8P42vqGbd6PD7Q= +github.com/platform-mesh/golang-commons v0.6.0/go.mod h1:Xxo0xmBHKiEUW2iAzs4BU7NPSJB2NkXg7NoGnlqEi40= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= @@ -239,8 +231,8 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/goleak v1.3.1-0.20241121203838-4ff5fa6529ee h1:uOMbcH1Dmxv45VkkpZQYoerZFeDncWpjbN7ATiQOO7c= +go.uber.org/goleak v1.3.1-0.20241121203838-4ff5fa6529ee/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= @@ -252,25 +244,23 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= -golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= +golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= -golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -279,20 +269,20 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -318,21 +308,33 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apiextensions-apiserver v0.33.3 h1:qmOcAHN6DjfD0v9kxL5udB27SRP6SG/MTopmge3MwEs= -k8s.io/apiextensions-apiserver v0.33.3/go.mod h1:oROuctgo27mUsyp9+Obahos6CWcMISSAPzQ77CAQGz8= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= 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-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= 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/controller-runtime v0.22.2 h1:cK2l8BGWsSWkXz09tcS4rJh95iOLney5eawcK5A33r4= +sigs.k8s.io/controller-runtime v0.22.2/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/multicluster-runtime v0.21.0-alpha.8 h1:Pq69tTKfN8ADw8m8A3wUtP8wJ9SPQbbOsgapm3BZEPw= +sigs.k8s.io/multicluster-runtime v0.21.0-alpha.8/go.mod h1:CpBzLMLQKdm+UCchd2FiGPiDdCxM5dgCCPKuaQ6Fsv0= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/config/config.go b/internal/config/config.go index 9942f9a..e0529ff 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,6 +11,7 @@ type Config struct { BaseDomain string `mapstructure:"base-domain" default:"portal.dev.local:8443"` GroupClaim string `mapstructure:"group-claim" default:"groups"` UserClaim string `mapstructure:"user-claim" default:"email"` + InitializerName string `mapstructure:"initializer-name" default:"root:security"` DomainCALookup bool `mapstructure:"domain-ca-lookup" default:"false"` IDP struct { // SMTP settings @@ -26,5 +27,7 @@ type Config struct { SMTPUser string `mapstructure:"idp-smtp-user"` SMTPPasswordSecretName string `mapstructure:"idp-smtp-password-secret-name"` SMTPPasswordSecretKey string `mapstructure:"idp-smtp-password-secret-key" default:"password"` + + AdditionalRedirectURLs []string `mapstructure:"idp-additional-redirect-urls"` } `mapstructure:",squash"` } diff --git a/internal/controller/apibinding_controller.go b/internal/controller/apibinding_controller.go index bd3af5a..0a48e09 100644 --- a/internal/controller/apibinding_controller.go +++ b/internal/controller/apibinding_controller.go @@ -5,38 +5,39 @@ import ( kcpv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1" platformeshconfig "github.com/platform-mesh/golang-commons/config" - lifecyclecontrollerruntime "github.com/platform-mesh/golang-commons/controller/lifecycle/controllerruntime" + "github.com/platform-mesh/golang-commons/controller/lifecycle/builder" lifecyclesubroutine "github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine" "github.com/platform-mesh/golang-commons/logger" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/kcp" + "sigs.k8s.io/controller-runtime/pkg/predicate" + lifecyclecontrollerruntime "github.com/platform-mesh/golang-commons/controller/lifecycle/multicluster" "github.com/platform-mesh/security-operator/internal/subroutine" + mccontext "sigs.k8s.io/multicluster-runtime/pkg/context" + mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" + mcreconcile "sigs.k8s.io/multicluster-runtime/pkg/reconcile" ) -func NewAPIBindingReconciler(cl client.Client, logger *logger.Logger, lcClientFunc subroutine.NewLogicalClusterClientFunc) *APIBindingReconciler { +func NewAPIBindingReconciler(logger *logger.Logger, mcMgr mcmanager.Manager) *APIBindingReconciler { return &APIBindingReconciler{ - lifecycle: lifecyclecontrollerruntime.NewLifecycleManager( - []lifecyclesubroutine.Subroutine{ - subroutine.NewAuthorizationModelGenerationSubroutine(cl, lcClientFunc), - }, - "apibinding", - "apibinding", - cl, - logger, - ), + log: logger, + lifecycle: builder.NewBuilder("apibinding", "apibinding-controller", []lifecyclesubroutine.Subroutine{ + subroutine.NewAuthorizationModelGenerationSubroutine(mcMgr), + }, logger). + BuildMultiCluster(mcMgr), } } type APIBindingReconciler struct { + log *logger.Logger lifecycle *lifecyclecontrollerruntime.LifecycleManager } -func (r *APIBindingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.lifecycle.Reconcile(ctx, req, &kcpv1alpha1.APIBinding{}) +func (r *APIBindingReconciler) Reconcile(ctx context.Context, req mcreconcile.Request) (ctrl.Result, error) { + ctxWithCluster := mccontext.WithCluster(ctx, req.ClusterName) + return r.lifecycle.Reconcile(ctxWithCluster, req, &kcpv1alpha1.APIBinding{}) } -func (r *APIBindingReconciler) SetupWithManager(mgr ctrl.Manager, logger *logger.Logger, cfg *platformeshconfig.CommonServiceConfig) error { - return r.lifecycle.SetupWithManager(mgr, cfg.MaxConcurrentReconciles, "apibinding-controller", &kcpv1alpha1.APIBinding{}, cfg.DebugLabelValue, kcp.WithClusterInContext(r), logger) +func (r *APIBindingReconciler) SetupWithManager(mgr mcmanager.Manager, cfg *platformeshconfig.CommonServiceConfig, evp ...predicate.Predicate) error { + return r.lifecycle.SetupWithManager(mgr, cfg.MaxConcurrentReconciles, "apibinding-controller", &kcpv1alpha1.APIBinding{}, cfg.DebugLabelValue, r, r.log, evp...) } diff --git a/internal/controller/authorization_model_controller.go b/internal/controller/authorization_model_controller.go index e69f36b..f8e0e79 100644 --- a/internal/controller/authorization_model_controller.go +++ b/internal/controller/authorization_model_controller.go @@ -5,48 +5,39 @@ import ( openfgav1 "github.com/openfga/api/proto/openfga/v1" platformeshconfig "github.com/platform-mesh/golang-commons/config" - lifecyclecontrollerruntime "github.com/platform-mesh/golang-commons/controller/lifecycle/controllerruntime" + "github.com/platform-mesh/golang-commons/controller/lifecycle/builder" + lifecyclecontrollerruntime "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" corev1alpha1 "github.com/platform-mesh/security-operator/api/v1alpha1" "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/kcp" + "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" ) type AuthorizationModelReconciler struct { + log *logger.Logger lifecycle *lifecyclecontrollerruntime.LifecycleManager } -func NewAuthorizationModelReconciler(log *logger.Logger, clt client.Client, fga openfgav1.OpenFGAServiceClient, lcClientFunc subroutine.NewLogicalClusterClientFunc) *AuthorizationModelReconciler { +func NewAuthorizationModelReconciler(log *logger.Logger, fga openfgav1.OpenFGAServiceClient, mcMgr mcmanager.Manager) *AuthorizationModelReconciler { return &AuthorizationModelReconciler{ - lifecycle: lifecyclecontrollerruntime.NewLifecycleManager( - []lifecyclesubroutine.Subroutine{ - subroutine.NewTupleSubroutine(fga, clt, lcClientFunc), - }, - "authorizationmodel", - "AuthorizationModelReconciler", - clt, - log, - ), + log: log, + lifecycle: builder.NewBuilder("authorizationmodel", "AuthorizationModelReconciler", []lifecyclesubroutine.Subroutine{ + subroutine.NewTupleSubroutine(fga, mcMgr), + }, log). + BuildMultiCluster(mcMgr), } } -func (r *AuthorizationModelReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.lifecycle.Reconcile(ctx, req, &corev1alpha1.AuthorizationModel{}) +func (r *AuthorizationModelReconciler) Reconcile(ctx context.Context, req mcreconcile.Request) (ctrl.Result, error) { + ctxWithCluster := mccontext.WithCluster(ctx, req.ClusterName) + return r.lifecycle.Reconcile(ctxWithCluster, req, &corev1alpha1.AuthorizationModel{}) } -func (r *AuthorizationModelReconciler) SetupWithManager(mgr ctrl.Manager, cfg *platformeshconfig.CommonServiceConfig, log *logger.Logger) error { // coverage-ignore - return r.lifecycle. - WithConditionManagement(). - SetupWithManager( - mgr, - cfg.MaxConcurrentReconciles, - "authorizationmodel", - &corev1alpha1.AuthorizationModel{}, - cfg.DebugLabelValue, - kcp.WithClusterInContext(r), - log, - ) +func (r *AuthorizationModelReconciler) SetupWithManager(mgr mcmanager.Manager, cfg *platformeshconfig.CommonServiceConfig, evp ...predicate.Predicate) error { // coverage-ignore + return r.lifecycle.SetupWithManager(mgr, cfg.MaxConcurrentReconciles, "authorizationmodel", &corev1alpha1.AuthorizationModel{}, cfg.DebugLabelValue, r, r.log, evp...) } diff --git a/internal/controller/initializer_controller.go b/internal/controller/initializer_controller.go index faff003..8d44f5b 100644 --- a/internal/controller/initializer_controller.go +++ b/internal/controller/initializer_controller.go @@ -5,50 +5,46 @@ import ( kcpcorev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1" platformeshconfig "github.com/platform-mesh/golang-commons/config" - lifecyclecontrollerruntime "github.com/platform-mesh/golang-commons/controller/lifecycle/controllerruntime" + "github.com/platform-mesh/golang-commons/controller/lifecycle/builder" + lifecyclecontrollerruntime "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" - "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/kcp" + "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" "github.com/platform-mesh/security-operator/internal/config" "github.com/platform-mesh/security-operator/internal/subroutine" ) type LogicalClusterReconciler struct { + log *logger.Logger + lifecycle *lifecyclecontrollerruntime.LifecycleManager } -func NewLogicalClusterReconciler(log *logger.Logger, restCfg *rest.Config, cl, orgClient client.Client, cfg config.Config, inClusterClient client.Client) *LogicalClusterReconciler { +func NewLogicalClusterReconciler(log *logger.Logger, orgClient client.Client, cfg config.Config, inClusterClient client.Client, mgr mcmanager.Manager) *LogicalClusterReconciler { return &LogicalClusterReconciler{ - lifecycle: lifecyclecontrollerruntime.NewLifecycleManager( - []lifecyclesubroutine.Subroutine{ - subroutine.NewWorkspaceInitializer(cl, orgClient, restCfg, cfg), - subroutine.NewWorkspaceAuthConfigurationSubroutine(orgClient, inClusterClient, cfg), - subroutine.NewRealmSubroutine(inClusterClient, &cfg, cfg.BaseDomain), - }, - "logicalcluster", - "LogicalClusterReconciler", - cl, - log, - ), + log: log, + lifecycle: builder.NewBuilder("logicalcluster", "LogicalClusterReconciler", []lifecyclesubroutine.Subroutine{ + subroutine.NewWorkspaceInitializer(orgClient, cfg, mgr), + subroutine.NewWorkspaceAuthConfigurationSubroutine(orgClient, inClusterClient, cfg), + subroutine.NewRealmSubroutine(inClusterClient, &cfg, cfg.BaseDomain), + subroutine.NewRemoveInitializer(mgr, cfg.InitializerName), + }, log). + WithReadOnly(). + BuildMultiCluster(mgr), } } -func (r *LogicalClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.lifecycle.Reconcile(ctx, req, &kcpcorev1alpha1.LogicalCluster{}) +func (r *LogicalClusterReconciler) Reconcile(ctx context.Context, req mcreconcile.Request) (ctrl.Result, error) { + ctxWithCluster := mccontext.WithCluster(ctx, req.ClusterName) + return r.lifecycle.Reconcile(ctxWithCluster, req, &kcpcorev1alpha1.LogicalCluster{}) } -func (r *LogicalClusterReconciler) SetupWithManager(mgr ctrl.Manager, cfg *platformeshconfig.CommonServiceConfig, log *logger.Logger) error { - return r.lifecycle.WithReadOnly().SetupWithManager( - mgr, - cfg.MaxConcurrentReconciles, - "logicalcluster", - &kcpcorev1alpha1.LogicalCluster{}, - cfg.DebugLabelValue, - kcp.WithClusterInContext(r), - log, - ) +func (r *LogicalClusterReconciler) SetupWithManager(mgr mcmanager.Manager, cfg *platformeshconfig.CommonServiceConfig, evp ...predicate.Predicate) error { + return r.lifecycle.SetupWithManager(mgr, cfg.MaxConcurrentReconciles, "LogicalCluster", &kcpcorev1alpha1.LogicalCluster{}, cfg.DebugLabelValue, r, r.log, evp...) } diff --git a/internal/controller/store_controller.go b/internal/controller/store_controller.go index dad5ccc..c2a043c 100644 --- a/internal/controller/store_controller.go +++ b/internal/controller/store_controller.go @@ -3,78 +3,64 @@ package controller import ( "context" + "github.com/platform-mesh/golang-commons/controller/lifecycle/builder" "k8s.io/apimachinery/pkg/types" 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/handler" - "sigs.k8s.io/controller-runtime/pkg/kcp" - "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/reconcile" + mcbuilder "sigs.k8s.io/multicluster-runtime/pkg/builder" + 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/kcp/sdk/apis/core/v1alpha1" - "github.com/kcp-dev/logicalcluster/v3" openfgav1 "github.com/openfga/api/proto/openfga/v1" platformeshconfig "github.com/platform-mesh/golang-commons/config" - lifecyclecontrollerruntime "github.com/platform-mesh/golang-commons/controller/lifecycle/controllerruntime" + lifecyclecontrollerruntime "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" corev1alpha1 "github.com/platform-mesh/security-operator/api/v1alpha1" "github.com/platform-mesh/security-operator/internal/subroutine" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/multicluster-runtime/pkg/handler" ) // StoreReconciler reconciles a Store object type StoreReconciler struct { - lifecycle *lifecyclecontrollerruntime.LifecycleManager - lcClientFunc subroutine.NewLogicalClusterClientFunc + fga openfgav1.OpenFGAServiceClient + log *logger.Logger + lifecycle *lifecyclecontrollerruntime.LifecycleManager } -func NewStoreReconciler(log *logger.Logger, clt client.Client, fga openfgav1.OpenFGAServiceClient, lcClientFunc subroutine.NewLogicalClusterClientFunc) *StoreReconciler { +func NewStoreReconciler(log *logger.Logger, fga openfgav1.OpenFGAServiceClient, mcMgr mcmanager.Manager) *StoreReconciler { return &StoreReconciler{ - lcClientFunc: lcClientFunc, - lifecycle: lifecyclecontrollerruntime.NewLifecycleManager( - []lifecyclesubroutine.Subroutine{ - subroutine.NewStoreSubroutine(fga, clt, lcClientFunc), - subroutine.NewAuthorizationModelSubroutine(fga, clt, lcClientFunc), - subroutine.NewTupleSubroutine(fga, clt, lcClientFunc), - }, - "store", - "StoreReconciler", - clt, - log, - ), + fga: fga, + log: log, + lifecycle: builder.NewBuilder("store", "StoreReconciler", []lifecyclesubroutine.Subroutine{ + subroutine.NewStoreSubroutine(fga, mcMgr), + subroutine.NewAuthorizationModelSubroutine(fga, mcMgr), + subroutine.NewTupleSubroutine(fga, mcMgr), + }, log).WithConditionManagement(). + BuildMultiCluster(mcMgr), } } -func (r *StoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.lifecycle.Reconcile(ctx, req, &corev1alpha1.Store{}) +func (r *StoreReconciler) Reconcile(ctx context.Context, req mcreconcile.Request) (ctrl.Result, error) { + ctxWithCluster := mccontext.WithCluster(ctx, req.ClusterName) + return r.lifecycle.Reconcile(ctxWithCluster, req, &corev1alpha1.Store{}) } // SetupWithManager sets up the controller with the Manager. -func (r *StoreReconciler) SetupWithManager(mgr ctrl.Manager, cfg *platformeshconfig.CommonServiceConfig, log *logger.Logger) error { // coverage-ignore - controllerBuilder, err := r.lifecycle. - WithConditionManagement(). - SetupWithManagerBuilder(mgr, cfg.MaxConcurrentReconciles, "store", &corev1alpha1.Store{}, cfg.DebugLabelValue, log) +func (r *StoreReconciler) SetupWithManager(mgr mcmanager.Manager, cfg *platformeshconfig.CommonServiceConfig, evp ...predicate.Predicate) error { // coverage-ignore + builder, err := r.lifecycle.SetupWithManagerBuilder(mgr, cfg.MaxConcurrentReconciles, "store", &corev1alpha1.Store{}, cfg.DebugLabelValue, r.log, evp...) if err != nil { return err } - - return controllerBuilder. + return builder. Watches( &corev1alpha1.AuthorizationModel{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { - model := obj.(*corev1alpha1.AuthorizationModel) - - lcClient, err := r.lcClientFunc(logicalcluster.Name(model.Spec.StoreRef.Path)) - if err != nil { - log.Error().Err(err).Msg("failed to get logical cluster client") - return nil - } - - var lc kcpcorev1alpha1.LogicalCluster - err = lcClient.Get(ctx, client.ObjectKey{Name: "cluster"}, &lc) - if err != nil { - log.Error().Err(err).Msg("failed to get logical cluster") + model,ok := obj.(*corev1alpha1.AuthorizationModel) + if !ok { return nil } @@ -83,11 +69,9 @@ func (r *StoreReconciler) SetupWithManager(mgr ctrl.Manager, cfg *platformeshcon NamespacedName: types.NamespacedName{ Name: model.Spec.StoreRef.Name, }, - ClusterName: lc.Annotations["kcp.io/cluster"], }, } }), - builder.WithPredicates(predicate.GenerationChangedPredicate{}), - ). - Complete(kcp.WithClusterInContext(r)) + mcbuilder.WithPredicates(predicate.GenerationChangedPredicate{}), + ).Complete(r) } diff --git a/internal/subroutine/authorization_model.go b/internal/subroutine/authorization_model.go index 650713d..a90ffdd 100644 --- a/internal/subroutine/authorization_model.go +++ b/internal/subroutine/authorization_model.go @@ -4,8 +4,6 @@ import ( "context" "fmt" - kcpcorev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1" - "github.com/kcp-dev/logicalcluster/v3" openfgav1 "github.com/openfga/api/proto/openfga/v1" language "github.com/openfga/language/pkg/go/transformer" "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" @@ -16,29 +14,28 @@ import ( "google.golang.org/protobuf/encoding/protojson" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/kontext" "sigs.k8s.io/controller-runtime/pkg/reconcile" + mccontext "sigs.k8s.io/multicluster-runtime/pkg/context" + mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" ) const schemaVersion = "1.2" type authorizationModelSubroutine struct { - fga openfgav1.OpenFGAServiceClient - k8s client.Client - lcClientFunc NewLogicalClusterClientFunc + fga openfgav1.OpenFGAServiceClient + mgr mcmanager.Manager } -func NewAuthorizationModelSubroutine(fga openfgav1.OpenFGAServiceClient, k8s client.Client, lcClientFunc NewLogicalClusterClientFunc) *authorizationModelSubroutine { +func NewAuthorizationModelSubroutine(fga openfgav1.OpenFGAServiceClient, mgr mcmanager.Manager) *authorizationModelSubroutine { return &authorizationModelSubroutine{ - fga: fga, - k8s: k8s, - lcClientFunc: lcClientFunc, + fga: fga, + mgr: mgr, } } var _ subroutine.Subroutine = &authorizationModelSubroutine{} -func (a *authorizationModelSubroutine) Finalizers() []string { return nil } +func (a *authorizationModelSubroutine) Finalizers(_ runtimeobject.RuntimeObject) []string { return nil } func (a *authorizationModelSubroutine) GetName() string { return "AuthorizationModel" } @@ -46,29 +43,14 @@ func (a *authorizationModelSubroutine) Finalize(ctx context.Context, instance ru return ctrl.Result{}, nil } -type NewLogicalClusterClientFunc func(clusterKey logicalcluster.Name) (client.Client, error) +func getRelatedAuthorizationModels(ctx context.Context, k8s client.Client, store *v1alpha1.Store) (v1alpha1.AuthorizationModelList, error) { -func getRelatedAuthorizationModels(ctx context.Context, k8s client.Client, store *v1alpha1.Store, lcCLientFunc NewLogicalClusterClientFunc) (v1alpha1.AuthorizationModelList, error) { - - storeClusterKey, ok := kontext.ClusterFrom(ctx) + storeClusterKey, ok := mccontext.ClusterFrom(ctx) if !ok { return v1alpha1.AuthorizationModelList{}, fmt.Errorf("unable to get cluster key from context") } - lcClient, err := lcCLientFunc(storeClusterKey) - if err != nil { - return v1alpha1.AuthorizationModelList{}, err - } - - var lc kcpcorev1alpha1.LogicalCluster - err = lcClient.Get(ctx, client.ObjectKey{Name: "cluster"}, &lc) - if err != nil { - return v1alpha1.AuthorizationModelList{}, err - } - - storeWorkspacePath := lc.Annotations["kcp.io/cluster"] - - allCtx := kontext.WithCluster(ctx, "") + allCtx := mccontext.WithCluster(ctx, "") allAuthorizationModels := v1alpha1.AuthorizationModelList{} if err := k8s.List(allCtx, &allAuthorizationModels); err != nil { @@ -77,7 +59,7 @@ func getRelatedAuthorizationModels(ctx context.Context, k8s client.Client, store var extendingModules v1alpha1.AuthorizationModelList for _, model := range allAuthorizationModels.Items { - if model.Spec.StoreRef.Name != store.Name || model.Spec.StoreRef.Path != storeWorkspacePath { + if model.Spec.StoreRef.Name != store.Name || model.Spec.StoreRef.Path != storeClusterKey { continue } @@ -91,7 +73,12 @@ func (a *authorizationModelSubroutine) Process(ctx context.Context, instance run log := logger.LoadLoggerFromContext(ctx) store := instance.(*v1alpha1.Store) - extendingModules, err := getRelatedAuthorizationModels(ctx, a.k8s, store, a.lcClientFunc) + cluster, err := a.mgr.ClusterFromContext(ctx) + if err != nil { + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("unable to get cluster from context: %w", err), true, false) + } + + extendingModules, err := getRelatedAuthorizationModels(ctx, cluster.GetClient(), store) if err != nil { log.Error().Err(err).Msg("unable to get related authorization models") return ctrl.Result{}, errors.NewOperatorError(err, true, false) diff --git a/internal/subroutine/authorization_model_generation.go b/internal/subroutine/authorization_model_generation.go index b4b5d6b..9c23b5c 100644 --- a/internal/subroutine/authorization_model_generation.go +++ b/internal/subroutine/authorization_model_generation.go @@ -19,24 +19,22 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" securityv1alpha1 "github.com/platform-mesh/security-operator/api/v1alpha1" ) -func NewAuthorizationModelGenerationSubroutine(cl client.Client, lcClientFunc NewLogicalClusterClientFunc) *AuthorizationModelGenerationSubroutine { +func NewAuthorizationModelGenerationSubroutine(mcMgr mcmanager.Manager) *AuthorizationModelGenerationSubroutine { return &AuthorizationModelGenerationSubroutine{ - cl: cl, - lcClientFunc: lcClientFunc, + mgr: mcMgr, } } var _ lifecyclesubroutine.Subroutine = &AuthorizationModelGenerationSubroutine{} type AuthorizationModelGenerationSubroutine struct { - cl client.Client - lcClientFunc NewLogicalClusterClientFunc + mgr mcmanager.Manager } var modelTpl = template.Must(template.New("model").Parse(`module {{ .Name }} @@ -85,14 +83,19 @@ func (a *AuthorizationModelGenerationSubroutine) Finalize(ctx context.Context, i bindingToDelete := instance.(*kcpv1alpha1.APIBinding) + cluster, err := a.mgr.ClusterFromContext(ctx) + if err != nil { + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("unable to get cluster from context: %w", err), true, false) + } + var bindings kcpv1alpha1.APIBindingList - err := a.cl.List(ctx, &bindings) + err = cluster.GetClient().List(ctx, &bindings) if err != nil { return ctrl.Result{}, errors.NewOperatorError(err, true, true) } var toDeleteAccountInfo accountv1alpha1.AccountInfo - err = a.cl.Get(ctx, types.NamespacedName{Name: "account"}, &toDeleteAccountInfo) + err = cluster.GetClient().Get(ctx, types.NamespacedName{Name: "account"}, &toDeleteAccountInfo) if err != nil { log.Error().Err(err).Msg("unable to get account info for binding deletion") return ctrl.Result{}, errors.NewOperatorError(err, true, true) @@ -104,13 +107,13 @@ func (a *AuthorizationModelGenerationSubroutine) Finalize(ctx context.Context, i continue } - bindingWsClient, err := a.lcClientFunc(logicalcluster.From(&binding)) + bindingCluster, err := a.mgr.GetCluster(ctx, string(logicalcluster.From(&binding))) if err != nil { return ctrl.Result{}, errors.NewOperatorError(err, true, true) } var accountInfo accountv1alpha1.AccountInfo - err = bindingWsClient.Get(ctx, types.NamespacedName{Name: "account"}, &accountInfo) + err = bindingCluster.GetClient().Get(ctx, types.NamespacedName{Name: "account"}, &accountInfo) if kerrors.IsNotFound(err) || meta.IsNoMatchError(err) { // If the accountinfo does not exist, we can skip the model generation. return ctrl.Result{}, nil @@ -134,7 +137,7 @@ func (a *AuthorizationModelGenerationSubroutine) Finalize(ctx context.Context, i return ctrl.Result{}, nil } - err = a.cl.Delete(ctx, &securityv1alpha1.AuthorizationModel{ + err = cluster.GetClient().Delete(ctx, &securityv1alpha1.AuthorizationModel{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("%s-%s", bindingToDelete.Spec.Reference.Export.Name, bindingToDelete.Spec.Reference.Export.Path), }, @@ -152,7 +155,9 @@ func (a *AuthorizationModelGenerationSubroutine) Finalize(ctx context.Context, i } // Finalizers implements lifecycle.Subroutine. -func (a *AuthorizationModelGenerationSubroutine) Finalizers() []string { return []string{} } +func (a *AuthorizationModelGenerationSubroutine) Finalizers(_ lifecyclecontrollerruntime.RuntimeObject) []string { + return []string{} +} // GetName implements lifecycle.Subroutine. func (a *AuthorizationModelGenerationSubroutine) GetName() string { @@ -163,13 +168,13 @@ func (a *AuthorizationModelGenerationSubroutine) GetName() string { func (a *AuthorizationModelGenerationSubroutine) Process(ctx context.Context, instance lifecyclecontrollerruntime.RuntimeObject) (ctrl.Result, errors.OperatorError) { binding := instance.(*kcpv1alpha1.APIBinding) - bindingWsClient, err := a.lcClientFunc(logicalcluster.From(binding)) + cluster, err := a.mgr.ClusterFromContext(ctx) if err != nil { return ctrl.Result{}, errors.NewOperatorError(err, true, true) } var accountInfo accountv1alpha1.AccountInfo - err = bindingWsClient.Get(ctx, types.NamespacedName{Name: "account"}, &accountInfo) + err = cluster.GetClient().Get(ctx, types.NamespacedName{Name: "account"}, &accountInfo) if kerrors.IsNotFound(err) || meta.IsNoMatchError(err) { // If the accountinfo does not exist, we can skip the model generation. return ctrl.Result{}, nil @@ -178,25 +183,25 @@ func (a *AuthorizationModelGenerationSubroutine) Process(ctx context.Context, in return ctrl.Result{}, errors.NewOperatorError(err, true, true) } - apiExportClient, err := a.lcClientFunc(logicalcluster.Name(binding.Spec.Reference.Export.Path)) - if err != nil { - return ctrl.Result{}, errors.NewOperatorError(err, true, true) - } - if binding.Spec.Reference.Export.Name == "core.platform-mesh.io" || strings.HasSuffix(binding.Spec.Reference.Export.Name, "kcp.io") { // If the APIExport is the core.platform-mesh.io, we can skip the model generation. return ctrl.Result{}, nil } + apiExportCluster, err := a.mgr.GetCluster(ctx, binding.Status.APIExportClusterName) + if err != nil { + return ctrl.Result{}, errors.NewOperatorError(err, true, true) + } + var apiExport kcpv1alpha1.APIExport - err = apiExportClient.Get(ctx, types.NamespacedName{Name: binding.Spec.Reference.Export.Name}, &apiExport) + err = apiExportCluster.GetClient().Get(ctx, types.NamespacedName{Name: binding.Spec.Reference.Export.Name}, &apiExport) if err != nil { return ctrl.Result{}, errors.NewOperatorError(err, true, true) } for _, latestResourceSchema := range apiExport.Spec.LatestResourceSchemas { var resourceSchema kcpv1alpha1.APIResourceSchema - err := apiExportClient.Get(ctx, types.NamespacedName{Name: latestResourceSchema}, &resourceSchema) + err := apiExportCluster.GetClient().Get(ctx, types.NamespacedName{Name: latestResourceSchema}, &resourceSchema) if err != nil { return ctrl.Result{}, errors.NewOperatorError(err, true, true) } @@ -226,7 +231,7 @@ func (a *AuthorizationModelGenerationSubroutine) Process(ctx context.Context, in }, } - _, err = controllerutil.CreateOrUpdate(ctx, apiExportClient, &model, func() error { + _, err = controllerutil.CreateOrUpdate(ctx, apiExportCluster.GetClient(), &model, func() error { model.Spec = securityv1alpha1.AuthorizationModelSpec{ Model: buffer.String(), StoreRef: securityv1alpha1.WorkspaceStoreRef{ diff --git a/internal/subroutine/authorization_model_generation_test.go b/internal/subroutine/authorization_model_generation_test.go index c742556..94ec5ca 100644 --- a/internal/subroutine/authorization_model_generation_test.go +++ b/internal/subroutine/authorization_model_generation_test.go @@ -5,7 +5,6 @@ import ( "testing" kcpv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1" - "github.com/kcp-dev/logicalcluster/v3" "github.com/platform-mesh/security-operator/internal/subroutine" "github.com/platform-mesh/security-operator/internal/subroutine/mocks" "github.com/stretchr/testify/assert" @@ -41,7 +40,7 @@ func TestAuthorizationModelGeneration_Process(t *testing.T) { binding: &kcpv1alpha1.APIBinding{ Spec: kcpv1alpha1.APIBindingSpec{Reference: kcpv1alpha1.BindingReference{Export: &kcpv1alpha1.ExportBindingReference{Name: "foo", Path: "bar"}}}, }, - mockSetup: func(kcpClient *mocks.MockClient) {}, + mockSetup: func(kcpClient *mocks.MockClient) {}, expectError: true, }, { @@ -57,12 +56,15 @@ func TestAuthorizationModelGeneration_Process(t *testing.T) { }, }, { - name: "error on lcClientFunc for apiExport client", + name: "error on getting apiExport", binding: &kcpv1alpha1.APIBinding{ Spec: kcpv1alpha1.APIBindingSpec{Reference: kcpv1alpha1.BindingReference{Export: &kcpv1alpha1.ExportBindingReference{Name: "foo", Path: "bar"}}}, }, mockSetup: func(kcpClient *mocks.MockClient) { - kcpClient.EXPECT().Get(mock.Anything, mock.Anything, mock.Anything).Return(nil) + // account info exists + kcpClient.EXPECT().Get(mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + // apiExport Get fails + kcpClient.EXPECT().Get(mock.Anything, mock.Anything, mock.Anything).Return(assert.AnError) }, expectError: true, }, @@ -257,21 +259,21 @@ func TestAuthorizationModelGeneration_Process(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { + manager := mocks.NewMockManager(t) + cluster := mocks.NewMockCluster(t) kcpClient := mocks.NewMockClient(t) - lcFunc := func(clusterKey logicalcluster.Name) (client.Client, error) { - // Simulate lcClientFunc error for specific tests by checking name value - if test.name == "error on lcClientFunc for binding workspace client" { - return nil, assert.AnError - } - if test.name == "error on lcClientFunc for apiExport client" && string(clusterKey) == "bar" { - return nil, assert.AnError + + if test.name == "error on lcClientFunc for binding workspace client" { + manager.EXPECT().ClusterFromContext(mock.Anything).Return(nil, assert.AnError) + } else { + manager.EXPECT().ClusterFromContext(mock.Anything).Return(cluster, nil) + manager.EXPECT().GetCluster(mock.Anything, mock.Anything).Return(cluster, nil).Maybe() + cluster.EXPECT().GetClient().Return(kcpClient).Maybe() + if test.mockSetup != nil { + test.mockSetup(kcpClient) } - return kcpClient, nil } - if test.mockSetup != nil { - test.mockSetup(kcpClient) - } - sub := subroutine.NewAuthorizationModelGenerationSubroutine(kcpClient, lcFunc) + sub := subroutine.NewAuthorizationModelGenerationSubroutine(manager) ctx := context.Background() _, err := sub.Process(ctx, test.binding) if test.expectError { @@ -521,17 +523,21 @@ func TestAuthorizationModelGeneration_Finalize(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { + manager := mocks.NewMockManager(t) + cluster := mocks.NewMockCluster(t) kcpClient := mocks.NewMockClient(t) - lcFunc := func(clusterKey logicalcluster.Name) (client.Client, error) { - if test.name == "error on lcClientFunc for binding in Finalize" { - return nil, assert.AnError + + if test.name == "error on lcClientFunc for binding in Finalize" { + manager.EXPECT().ClusterFromContext(mock.Anything).Return(nil, assert.AnError) + } else { + manager.EXPECT().ClusterFromContext(mock.Anything).Return(cluster, nil) + manager.EXPECT().GetCluster(mock.Anything, mock.Anything).Return(cluster, nil).Maybe() + cluster.EXPECT().GetClient().Return(kcpClient).Maybe() + if test.mockSetup != nil { + test.mockSetup(kcpClient, test.binding) } - return kcpClient, nil - } - if test.mockSetup != nil { - test.mockSetup(kcpClient, test.binding) } - sub := subroutine.NewAuthorizationModelGenerationSubroutine(kcpClient, lcFunc) + sub := subroutine.NewAuthorizationModelGenerationSubroutine(manager) ctx := context.Background() _, err := sub.Finalize(ctx, test.binding) if test.expectError { @@ -544,6 +550,6 @@ func TestAuthorizationModelGeneration_Finalize(t *testing.T) { } func TestFinalizeAuthorizationModelGeneration(t *testing.T) { - finalizers := subroutine.NewAuthorizationModelGenerationSubroutine(nil, nil).Finalizers() + finalizers := subroutine.NewAuthorizationModelGenerationSubroutine(nil).Finalizers(nil) assert.Equal(t, []string{}, finalizers) } diff --git a/internal/subroutine/authorization_model_test.go b/internal/subroutine/authorization_model_test.go index 5691b71..aec841d 100644 --- a/internal/subroutine/authorization_model_test.go +++ b/internal/subroutine/authorization_model_test.go @@ -19,7 +19,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/kontext" + mccontext "sigs.k8s.io/multicluster-runtime/pkg/context" ) var coreModule = ` @@ -52,17 +52,17 @@ type user ` func TestAuthorizationModelGetName(t *testing.T) { - subroutine := subroutine.NewAuthorizationModelSubroutine(nil, nil, nil) + subroutine := subroutine.NewAuthorizationModelSubroutine(nil, nil) assert.Equal(t, "AuthorizationModel", subroutine.GetName()) } func TestAuthorizationModelFinalizers(t *testing.T) { - subroutine := subroutine.NewAuthorizationModelSubroutine(nil, nil, nil) - assert.Equal(t, []string(nil), subroutine.Finalizers()) + subroutine := subroutine.NewAuthorizationModelSubroutine(nil, nil) + assert.Equal(t, []string(nil), subroutine.Finalizers(nil)) } func TestAuthorizationModelFinalize(t *testing.T) { - subroutine := subroutine.NewAuthorizationModelSubroutine(nil, nil, nil) + subroutine := subroutine.NewAuthorizationModelSubroutine(nil, nil) _, err := subroutine.Finalize(context.Background(), nil) assert.Nil(t, err) } @@ -86,6 +86,7 @@ func TestAuthorizationModelProcess(t *testing.T) { store *v1alpha1.Store fgaMocks func(*mocks.MockOpenFGAServiceClient) k8sMocks func(*mocks.MockClient) + mgrMocks func(*mocks.MockManager) expectError bool }{ { @@ -102,8 +103,7 @@ func TestAuthorizationModelProcess(t *testing.T) { }, }, k8sMocks: func(k8s *mocks.MockClient) { - mockLogicalClusterGet(k8s) - k8s.EXPECT().List(mock.Anything, mock.Anything, mock.Anything).RunAndReturn( + k8s.EXPECT().List(mock.Anything, mock.Anything).RunAndReturn( func(ctx context.Context, ol client.ObjectList, lo ...client.ListOption) error { am := ol.(*v1alpha1.AuthorizationModelList) am.Items = []v1alpha1.AuthorizationModel{ @@ -160,8 +160,7 @@ func TestAuthorizationModelProcess(t *testing.T) { }, }, k8sMocks: func(k8s *mocks.MockClient) { - mockLogicalClusterGet(k8s) - k8s.EXPECT().List(mock.Anything, mock.Anything, mock.Anything).RunAndReturn( + k8s.EXPECT().List(mock.Anything, mock.Anything).RunAndReturn( func(ctx context.Context, ol client.ObjectList, lo ...client.ListOption) error { am := ol.(*v1alpha1.AuthorizationModelList) am.Items = []v1alpha1.AuthorizationModel{ @@ -196,8 +195,7 @@ func TestAuthorizationModelProcess(t *testing.T) { }, }, k8sMocks: func(k8s *mocks.MockClient) { - mockLogicalClusterGet(k8s) - k8s.EXPECT().List(mock.Anything, mock.Anything, mock.Anything).Return(errors.New("error")) + k8s.EXPECT().List(mock.Anything, mock.Anything).Return(errors.New("error")) }, expectError: true, }, @@ -216,8 +214,7 @@ func TestAuthorizationModelProcess(t *testing.T) { }, }, k8sMocks: func(k8s *mocks.MockClient) { - mockLogicalClusterGet(k8s) - k8s.EXPECT().List(mock.Anything, mock.Anything, mock.Anything).RunAndReturn( + k8s.EXPECT().List(mock.Anything, mock.Anything).RunAndReturn( func(ctx context.Context, ol client.ObjectList, lo ...client.ListOption) error { am := ol.(*v1alpha1.AuthorizationModelList) am.Items = []v1alpha1.AuthorizationModel{ @@ -251,8 +248,7 @@ func TestAuthorizationModelProcess(t *testing.T) { }, }, k8sMocks: func(k8s *mocks.MockClient) { - mockLogicalClusterGet(k8s) - k8s.EXPECT().List(mock.Anything, mock.Anything, mock.Anything).RunAndReturn( + k8s.EXPECT().List(mock.Anything, mock.Anything).RunAndReturn( func(ctx context.Context, ol client.ObjectList, lo ...client.ListOption) error { am := ol.(*v1alpha1.AuthorizationModelList) am.Items = []v1alpha1.AuthorizationModel{ @@ -286,8 +282,7 @@ func TestAuthorizationModelProcess(t *testing.T) { }, }, k8sMocks: func(k8s *mocks.MockClient) { - mockLogicalClusterGet(k8s) - k8s.EXPECT().List(mock.Anything, mock.Anything, mock.Anything).RunAndReturn( + k8s.EXPECT().List(mock.Anything, mock.Anything).RunAndReturn( func(ctx context.Context, ol client.ObjectList, lo ...client.ListOption) error { am := ol.(*v1alpha1.AuthorizationModelList) am.Items = []v1alpha1.AuthorizationModel{ @@ -319,16 +314,23 @@ func TestAuthorizationModelProcess(t *testing.T) { test.fgaMocks(fga) } - k8s := mocks.NewMockClient(t) + manager := mocks.NewMockManager(t) + if test.mgrMocks != nil { + test.mgrMocks(manager) + } + + cluster := mocks.NewMockCluster(t) + client := mocks.NewMockClient(t) + if test.k8sMocks != nil { - test.k8sMocks(k8s) + test.k8sMocks(client) } - subroutine := subroutine.NewAuthorizationModelSubroutine(fga, k8s, func(clusterKey logicalcluster.Name) (client.Client, error) { - return k8s, nil - }) + manager.EXPECT().ClusterFromContext(mock.Anything).Return(cluster, nil) + cluster.EXPECT().GetClient().Return(client) - ctx := kontext.WithCluster(context.Background(), logicalcluster.Name("a")) + subroutine := subroutine.NewAuthorizationModelSubroutine(fga, manager) + ctx := mccontext.WithCluster(context.Background(), string(logicalcluster.Name("path"))) _, err := subroutine.Process(ctx, test.store) if test.expectError { diff --git a/internal/subroutine/mocks/mock_Client.go b/internal/subroutine/mocks/mock_Client.go index f2736c9..6fc0bb3 100644 --- a/internal/subroutine/mocks/mock_Client.go +++ b/internal/subroutine/mocks/mock_Client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.0. DO NOT EDIT. +// Code generated by mockery v2.53.5. DO NOT EDIT. package mocks @@ -31,6 +31,68 @@ func (_m *MockClient) EXPECT() *MockClient_Expecter { return &MockClient_Expecter{mock: &_m.Mock} } +// Apply provides a mock function with given fields: ctx, obj, opts +func (_m *MockClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.ApplyOption) error { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, obj) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Apply") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, runtime.ApplyConfiguration, ...client.ApplyOption) error); ok { + r0 = rf(ctx, obj, opts...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockClient_Apply_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Apply' +type MockClient_Apply_Call struct { + *mock.Call +} + +// Apply is a helper method to define mock.On call +// - ctx context.Context +// - obj runtime.ApplyConfiguration +// - opts ...client.ApplyOption +func (_e *MockClient_Expecter) Apply(ctx interface{}, obj interface{}, opts ...interface{}) *MockClient_Apply_Call { + return &MockClient_Apply_Call{Call: _e.mock.On("Apply", + append([]interface{}{ctx, obj}, opts...)...)} +} + +func (_c *MockClient_Apply_Call) Run(run func(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.ApplyOption)) *MockClient_Apply_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]client.ApplyOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(client.ApplyOption) + } + } + run(args[0].(context.Context), args[1].(runtime.ApplyConfiguration), variadicArgs...) + }) + return _c +} + +func (_c *MockClient_Apply_Call) Return(_a0 error) *MockClient_Apply_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockClient_Apply_Call) RunAndReturn(run func(context.Context, runtime.ApplyConfiguration, ...client.ApplyOption) error) *MockClient_Apply_Call { + _c.Call.Return(run) + return _c +} + // Create provides a mock function with given fields: ctx, obj, opts func (_m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { _va := make([]interface{}, len(opts)) @@ -517,7 +579,7 @@ func (_c *MockClient_Patch_Call) RunAndReturn(run func(context.Context, client.O return _c } -// RESTMapper provides a mock function with given fields: +// RESTMapper provides a mock function with no fields func (_m *MockClient) RESTMapper() meta.RESTMapper { ret := _m.Called() @@ -564,7 +626,7 @@ func (_c *MockClient_RESTMapper_Call) RunAndReturn(run func() meta.RESTMapper) * return _c } -// Scheme provides a mock function with given fields: +// Scheme provides a mock function with no fields func (_m *MockClient) Scheme() *runtime.Scheme { ret := _m.Called() @@ -611,7 +673,7 @@ func (_c *MockClient_Scheme_Call) RunAndReturn(run func() *runtime.Scheme) *Mock return _c } -// Status provides a mock function with given fields: +// Status provides a mock function with no fields func (_m *MockClient) Status() client.SubResourceWriter { ret := _m.Called() diff --git a/internal/subroutine/mocks/mock_Cluster.go b/internal/subroutine/mocks/mock_Cluster.go new file mode 100644 index 0000000..442e4ae --- /dev/null +++ b/internal/subroutine/mocks/mock_Cluster.go @@ -0,0 +1,519 @@ +// Code generated by mockery v2.53.5. DO NOT EDIT. + +package mocks + +import ( + cache "sigs.k8s.io/controller-runtime/pkg/cache" + client "sigs.k8s.io/controller-runtime/pkg/client" + + context "context" + + http "net/http" + + meta "k8s.io/apimachinery/pkg/api/meta" + + mock "github.com/stretchr/testify/mock" + + record "k8s.io/client-go/tools/record" + + rest "k8s.io/client-go/rest" + + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// MockCluster is an autogenerated mock type for the Cluster type +type MockCluster struct { + mock.Mock +} + +type MockCluster_Expecter struct { + mock *mock.Mock +} + +func (_m *MockCluster) EXPECT() *MockCluster_Expecter { + return &MockCluster_Expecter{mock: &_m.Mock} +} + +// GetAPIReader provides a mock function with no fields +func (_m *MockCluster) GetAPIReader() client.Reader { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetAPIReader") + } + + var r0 client.Reader + if rf, ok := ret.Get(0).(func() client.Reader); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(client.Reader) + } + } + + return r0 +} + +// MockCluster_GetAPIReader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAPIReader' +type MockCluster_GetAPIReader_Call struct { + *mock.Call +} + +// GetAPIReader is a helper method to define mock.On call +func (_e *MockCluster_Expecter) GetAPIReader() *MockCluster_GetAPIReader_Call { + return &MockCluster_GetAPIReader_Call{Call: _e.mock.On("GetAPIReader")} +} + +func (_c *MockCluster_GetAPIReader_Call) Run(run func()) *MockCluster_GetAPIReader_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockCluster_GetAPIReader_Call) Return(_a0 client.Reader) *MockCluster_GetAPIReader_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockCluster_GetAPIReader_Call) RunAndReturn(run func() client.Reader) *MockCluster_GetAPIReader_Call { + _c.Call.Return(run) + return _c +} + +// GetCache provides a mock function with no fields +func (_m *MockCluster) GetCache() cache.Cache { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetCache") + } + + var r0 cache.Cache + if rf, ok := ret.Get(0).(func() cache.Cache); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(cache.Cache) + } + } + + return r0 +} + +// MockCluster_GetCache_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCache' +type MockCluster_GetCache_Call struct { + *mock.Call +} + +// GetCache is a helper method to define mock.On call +func (_e *MockCluster_Expecter) GetCache() *MockCluster_GetCache_Call { + return &MockCluster_GetCache_Call{Call: _e.mock.On("GetCache")} +} + +func (_c *MockCluster_GetCache_Call) Run(run func()) *MockCluster_GetCache_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockCluster_GetCache_Call) Return(_a0 cache.Cache) *MockCluster_GetCache_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockCluster_GetCache_Call) RunAndReturn(run func() cache.Cache) *MockCluster_GetCache_Call { + _c.Call.Return(run) + return _c +} + +// GetClient provides a mock function with no fields +func (_m *MockCluster) GetClient() client.Client { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetClient") + } + + var r0 client.Client + if rf, ok := ret.Get(0).(func() client.Client); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(client.Client) + } + } + + return r0 +} + +// MockCluster_GetClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetClient' +type MockCluster_GetClient_Call struct { + *mock.Call +} + +// GetClient is a helper method to define mock.On call +func (_e *MockCluster_Expecter) GetClient() *MockCluster_GetClient_Call { + return &MockCluster_GetClient_Call{Call: _e.mock.On("GetClient")} +} + +func (_c *MockCluster_GetClient_Call) Run(run func()) *MockCluster_GetClient_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockCluster_GetClient_Call) Return(_a0 client.Client) *MockCluster_GetClient_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockCluster_GetClient_Call) RunAndReturn(run func() client.Client) *MockCluster_GetClient_Call { + _c.Call.Return(run) + return _c +} + +// GetConfig provides a mock function with no fields +func (_m *MockCluster) GetConfig() *rest.Config { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetConfig") + } + + var r0 *rest.Config + if rf, ok := ret.Get(0).(func() *rest.Config); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*rest.Config) + } + } + + return r0 +} + +// MockCluster_GetConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetConfig' +type MockCluster_GetConfig_Call struct { + *mock.Call +} + +// GetConfig is a helper method to define mock.On call +func (_e *MockCluster_Expecter) GetConfig() *MockCluster_GetConfig_Call { + return &MockCluster_GetConfig_Call{Call: _e.mock.On("GetConfig")} +} + +func (_c *MockCluster_GetConfig_Call) Run(run func()) *MockCluster_GetConfig_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockCluster_GetConfig_Call) Return(_a0 *rest.Config) *MockCluster_GetConfig_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockCluster_GetConfig_Call) RunAndReturn(run func() *rest.Config) *MockCluster_GetConfig_Call { + _c.Call.Return(run) + return _c +} + +// GetEventRecorderFor provides a mock function with given fields: name +func (_m *MockCluster) GetEventRecorderFor(name string) record.EventRecorder { + ret := _m.Called(name) + + if len(ret) == 0 { + panic("no return value specified for GetEventRecorderFor") + } + + var r0 record.EventRecorder + if rf, ok := ret.Get(0).(func(string) record.EventRecorder); ok { + r0 = rf(name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(record.EventRecorder) + } + } + + return r0 +} + +// MockCluster_GetEventRecorderFor_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEventRecorderFor' +type MockCluster_GetEventRecorderFor_Call struct { + *mock.Call +} + +// GetEventRecorderFor is a helper method to define mock.On call +// - name string +func (_e *MockCluster_Expecter) GetEventRecorderFor(name interface{}) *MockCluster_GetEventRecorderFor_Call { + return &MockCluster_GetEventRecorderFor_Call{Call: _e.mock.On("GetEventRecorderFor", name)} +} + +func (_c *MockCluster_GetEventRecorderFor_Call) Run(run func(name string)) *MockCluster_GetEventRecorderFor_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockCluster_GetEventRecorderFor_Call) Return(_a0 record.EventRecorder) *MockCluster_GetEventRecorderFor_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockCluster_GetEventRecorderFor_Call) RunAndReturn(run func(string) record.EventRecorder) *MockCluster_GetEventRecorderFor_Call { + _c.Call.Return(run) + return _c +} + +// GetFieldIndexer provides a mock function with no fields +func (_m *MockCluster) GetFieldIndexer() client.FieldIndexer { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetFieldIndexer") + } + + var r0 client.FieldIndexer + if rf, ok := ret.Get(0).(func() client.FieldIndexer); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(client.FieldIndexer) + } + } + + return r0 +} + +// MockCluster_GetFieldIndexer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFieldIndexer' +type MockCluster_GetFieldIndexer_Call struct { + *mock.Call +} + +// GetFieldIndexer is a helper method to define mock.On call +func (_e *MockCluster_Expecter) GetFieldIndexer() *MockCluster_GetFieldIndexer_Call { + return &MockCluster_GetFieldIndexer_Call{Call: _e.mock.On("GetFieldIndexer")} +} + +func (_c *MockCluster_GetFieldIndexer_Call) Run(run func()) *MockCluster_GetFieldIndexer_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockCluster_GetFieldIndexer_Call) Return(_a0 client.FieldIndexer) *MockCluster_GetFieldIndexer_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockCluster_GetFieldIndexer_Call) RunAndReturn(run func() client.FieldIndexer) *MockCluster_GetFieldIndexer_Call { + _c.Call.Return(run) + return _c +} + +// GetHTTPClient provides a mock function with no fields +func (_m *MockCluster) GetHTTPClient() *http.Client { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetHTTPClient") + } + + var r0 *http.Client + if rf, ok := ret.Get(0).(func() *http.Client); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*http.Client) + } + } + + return r0 +} + +// MockCluster_GetHTTPClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetHTTPClient' +type MockCluster_GetHTTPClient_Call struct { + *mock.Call +} + +// GetHTTPClient is a helper method to define mock.On call +func (_e *MockCluster_Expecter) GetHTTPClient() *MockCluster_GetHTTPClient_Call { + return &MockCluster_GetHTTPClient_Call{Call: _e.mock.On("GetHTTPClient")} +} + +func (_c *MockCluster_GetHTTPClient_Call) Run(run func()) *MockCluster_GetHTTPClient_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockCluster_GetHTTPClient_Call) Return(_a0 *http.Client) *MockCluster_GetHTTPClient_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockCluster_GetHTTPClient_Call) RunAndReturn(run func() *http.Client) *MockCluster_GetHTTPClient_Call { + _c.Call.Return(run) + return _c +} + +// GetRESTMapper provides a mock function with no fields +func (_m *MockCluster) GetRESTMapper() meta.RESTMapper { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetRESTMapper") + } + + var r0 meta.RESTMapper + if rf, ok := ret.Get(0).(func() meta.RESTMapper); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(meta.RESTMapper) + } + } + + return r0 +} + +// MockCluster_GetRESTMapper_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRESTMapper' +type MockCluster_GetRESTMapper_Call struct { + *mock.Call +} + +// GetRESTMapper is a helper method to define mock.On call +func (_e *MockCluster_Expecter) GetRESTMapper() *MockCluster_GetRESTMapper_Call { + return &MockCluster_GetRESTMapper_Call{Call: _e.mock.On("GetRESTMapper")} +} + +func (_c *MockCluster_GetRESTMapper_Call) Run(run func()) *MockCluster_GetRESTMapper_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockCluster_GetRESTMapper_Call) Return(_a0 meta.RESTMapper) *MockCluster_GetRESTMapper_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockCluster_GetRESTMapper_Call) RunAndReturn(run func() meta.RESTMapper) *MockCluster_GetRESTMapper_Call { + _c.Call.Return(run) + return _c +} + +// GetScheme provides a mock function with no fields +func (_m *MockCluster) GetScheme() *runtime.Scheme { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetScheme") + } + + var r0 *runtime.Scheme + if rf, ok := ret.Get(0).(func() *runtime.Scheme); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*runtime.Scheme) + } + } + + return r0 +} + +// MockCluster_GetScheme_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetScheme' +type MockCluster_GetScheme_Call struct { + *mock.Call +} + +// GetScheme is a helper method to define mock.On call +func (_e *MockCluster_Expecter) GetScheme() *MockCluster_GetScheme_Call { + return &MockCluster_GetScheme_Call{Call: _e.mock.On("GetScheme")} +} + +func (_c *MockCluster_GetScheme_Call) Run(run func()) *MockCluster_GetScheme_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockCluster_GetScheme_Call) Return(_a0 *runtime.Scheme) *MockCluster_GetScheme_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockCluster_GetScheme_Call) RunAndReturn(run func() *runtime.Scheme) *MockCluster_GetScheme_Call { + _c.Call.Return(run) + return _c +} + +// Start provides a mock function with given fields: ctx +func (_m *MockCluster) Start(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Start") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockCluster_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' +type MockCluster_Start_Call struct { + *mock.Call +} + +// Start is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockCluster_Expecter) Start(ctx interface{}) *MockCluster_Start_Call { + return &MockCluster_Start_Call{Call: _e.mock.On("Start", ctx)} +} + +func (_c *MockCluster_Start_Call) Run(run func(ctx context.Context)) *MockCluster_Start_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockCluster_Start_Call) Return(_a0 error) *MockCluster_Start_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockCluster_Start_Call) RunAndReturn(run func(context.Context) error) *MockCluster_Start_Call { + _c.Call.Return(run) + return _c +} + +// NewMockCluster creates a new instance of MockCluster. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockCluster(t interface { + mock.TestingT + Cleanup(func()) +}) *MockCluster { + mock := &MockCluster{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/subroutine/mocks/mock_Manager.go b/internal/subroutine/mocks/mock_Manager.go new file mode 100644 index 0000000..73876da --- /dev/null +++ b/internal/subroutine/mocks/mock_Manager.go @@ -0,0 +1,837 @@ +// Code generated by mockery v2.53.5. DO NOT EDIT. + +package mocks + +import ( + client "sigs.k8s.io/controller-runtime/pkg/client" + cluster "sigs.k8s.io/controller-runtime/pkg/cluster" + + config "sigs.k8s.io/controller-runtime/pkg/config" + + context "context" + + healthz "sigs.k8s.io/controller-runtime/pkg/healthz" + + http "net/http" + + logr "github.com/go-logr/logr" + + manager "sigs.k8s.io/multicluster-runtime/pkg/manager" + + mock "github.com/stretchr/testify/mock" + + multicluster "sigs.k8s.io/multicluster-runtime/pkg/multicluster" + + pkgmanager "sigs.k8s.io/controller-runtime/pkg/manager" + + webhook "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +// MockManager is an autogenerated mock type for the Manager type +type MockManager struct { + mock.Mock +} + +type MockManager_Expecter struct { + mock *mock.Mock +} + +func (_m *MockManager) EXPECT() *MockManager_Expecter { + return &MockManager_Expecter{mock: &_m.Mock} +} + +// Add provides a mock function with given fields: _a0 +func (_m *MockManager) Add(_a0 manager.Runnable) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Add") + } + + var r0 error + if rf, ok := ret.Get(0).(func(manager.Runnable) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockManager_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add' +type MockManager_Add_Call struct { + *mock.Call +} + +// Add is a helper method to define mock.On call +// - _a0 manager.Runnable +func (_e *MockManager_Expecter) Add(_a0 interface{}) *MockManager_Add_Call { + return &MockManager_Add_Call{Call: _e.mock.On("Add", _a0)} +} + +func (_c *MockManager_Add_Call) Run(run func(_a0 manager.Runnable)) *MockManager_Add_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(manager.Runnable)) + }) + return _c +} + +func (_c *MockManager_Add_Call) Return(_a0 error) *MockManager_Add_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockManager_Add_Call) RunAndReturn(run func(manager.Runnable) error) *MockManager_Add_Call { + _c.Call.Return(run) + return _c +} + +// AddHealthzCheck provides a mock function with given fields: name, check +func (_m *MockManager) AddHealthzCheck(name string, check healthz.Checker) error { + ret := _m.Called(name, check) + + if len(ret) == 0 { + panic("no return value specified for AddHealthzCheck") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, healthz.Checker) error); ok { + r0 = rf(name, check) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockManager_AddHealthzCheck_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddHealthzCheck' +type MockManager_AddHealthzCheck_Call struct { + *mock.Call +} + +// AddHealthzCheck is a helper method to define mock.On call +// - name string +// - check healthz.Checker +func (_e *MockManager_Expecter) AddHealthzCheck(name interface{}, check interface{}) *MockManager_AddHealthzCheck_Call { + return &MockManager_AddHealthzCheck_Call{Call: _e.mock.On("AddHealthzCheck", name, check)} +} + +func (_c *MockManager_AddHealthzCheck_Call) Run(run func(name string, check healthz.Checker)) *MockManager_AddHealthzCheck_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(healthz.Checker)) + }) + return _c +} + +func (_c *MockManager_AddHealthzCheck_Call) Return(_a0 error) *MockManager_AddHealthzCheck_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockManager_AddHealthzCheck_Call) RunAndReturn(run func(string, healthz.Checker) error) *MockManager_AddHealthzCheck_Call { + _c.Call.Return(run) + return _c +} + +// AddMetricsServerExtraHandler provides a mock function with given fields: path, handler +func (_m *MockManager) AddMetricsServerExtraHandler(path string, handler http.Handler) error { + ret := _m.Called(path, handler) + + if len(ret) == 0 { + panic("no return value specified for AddMetricsServerExtraHandler") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, http.Handler) error); ok { + r0 = rf(path, handler) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockManager_AddMetricsServerExtraHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddMetricsServerExtraHandler' +type MockManager_AddMetricsServerExtraHandler_Call struct { + *mock.Call +} + +// AddMetricsServerExtraHandler is a helper method to define mock.On call +// - path string +// - handler http.Handler +func (_e *MockManager_Expecter) AddMetricsServerExtraHandler(path interface{}, handler interface{}) *MockManager_AddMetricsServerExtraHandler_Call { + return &MockManager_AddMetricsServerExtraHandler_Call{Call: _e.mock.On("AddMetricsServerExtraHandler", path, handler)} +} + +func (_c *MockManager_AddMetricsServerExtraHandler_Call) Run(run func(path string, handler http.Handler)) *MockManager_AddMetricsServerExtraHandler_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(http.Handler)) + }) + return _c +} + +func (_c *MockManager_AddMetricsServerExtraHandler_Call) Return(_a0 error) *MockManager_AddMetricsServerExtraHandler_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockManager_AddMetricsServerExtraHandler_Call) RunAndReturn(run func(string, http.Handler) error) *MockManager_AddMetricsServerExtraHandler_Call { + _c.Call.Return(run) + return _c +} + +// AddReadyzCheck provides a mock function with given fields: name, check +func (_m *MockManager) AddReadyzCheck(name string, check healthz.Checker) error { + ret := _m.Called(name, check) + + if len(ret) == 0 { + panic("no return value specified for AddReadyzCheck") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, healthz.Checker) error); ok { + r0 = rf(name, check) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockManager_AddReadyzCheck_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddReadyzCheck' +type MockManager_AddReadyzCheck_Call struct { + *mock.Call +} + +// AddReadyzCheck is a helper method to define mock.On call +// - name string +// - check healthz.Checker +func (_e *MockManager_Expecter) AddReadyzCheck(name interface{}, check interface{}) *MockManager_AddReadyzCheck_Call { + return &MockManager_AddReadyzCheck_Call{Call: _e.mock.On("AddReadyzCheck", name, check)} +} + +func (_c *MockManager_AddReadyzCheck_Call) Run(run func(name string, check healthz.Checker)) *MockManager_AddReadyzCheck_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(healthz.Checker)) + }) + return _c +} + +func (_c *MockManager_AddReadyzCheck_Call) Return(_a0 error) *MockManager_AddReadyzCheck_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockManager_AddReadyzCheck_Call) RunAndReturn(run func(string, healthz.Checker) error) *MockManager_AddReadyzCheck_Call { + _c.Call.Return(run) + return _c +} + +// ClusterFromContext provides a mock function with given fields: ctx +func (_m *MockManager) ClusterFromContext(ctx context.Context) (cluster.Cluster, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for ClusterFromContext") + } + + var r0 cluster.Cluster + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (cluster.Cluster, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) cluster.Cluster); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(cluster.Cluster) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockManager_ClusterFromContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClusterFromContext' +type MockManager_ClusterFromContext_Call struct { + *mock.Call +} + +// ClusterFromContext is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockManager_Expecter) ClusterFromContext(ctx interface{}) *MockManager_ClusterFromContext_Call { + return &MockManager_ClusterFromContext_Call{Call: _e.mock.On("ClusterFromContext", ctx)} +} + +func (_c *MockManager_ClusterFromContext_Call) Run(run func(ctx context.Context)) *MockManager_ClusterFromContext_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockManager_ClusterFromContext_Call) Return(_a0 cluster.Cluster, _a1 error) *MockManager_ClusterFromContext_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockManager_ClusterFromContext_Call) RunAndReturn(run func(context.Context) (cluster.Cluster, error)) *MockManager_ClusterFromContext_Call { + _c.Call.Return(run) + return _c +} + +// Elected provides a mock function with no fields +func (_m *MockManager) Elected() <-chan struct{} { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Elected") + } + + var r0 <-chan struct{} + if rf, ok := ret.Get(0).(func() <-chan struct{}); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan struct{}) + } + } + + return r0 +} + +// MockManager_Elected_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Elected' +type MockManager_Elected_Call struct { + *mock.Call +} + +// Elected is a helper method to define mock.On call +func (_e *MockManager_Expecter) Elected() *MockManager_Elected_Call { + return &MockManager_Elected_Call{Call: _e.mock.On("Elected")} +} + +func (_c *MockManager_Elected_Call) Run(run func()) *MockManager_Elected_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockManager_Elected_Call) Return(_a0 <-chan struct{}) *MockManager_Elected_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockManager_Elected_Call) RunAndReturn(run func() <-chan struct{}) *MockManager_Elected_Call { + _c.Call.Return(run) + return _c +} + +// Engage provides a mock function with given fields: _a0, _a1, _a2 +func (_m *MockManager) Engage(_a0 context.Context, _a1 string, _a2 cluster.Cluster) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for Engage") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, cluster.Cluster) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockManager_Engage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Engage' +type MockManager_Engage_Call struct { + *mock.Call +} + +// Engage is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +// - _a2 cluster.Cluster +func (_e *MockManager_Expecter) Engage(_a0 interface{}, _a1 interface{}, _a2 interface{}) *MockManager_Engage_Call { + return &MockManager_Engage_Call{Call: _e.mock.On("Engage", _a0, _a1, _a2)} +} + +func (_c *MockManager_Engage_Call) Run(run func(_a0 context.Context, _a1 string, _a2 cluster.Cluster)) *MockManager_Engage_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(cluster.Cluster)) + }) + return _c +} + +func (_c *MockManager_Engage_Call) Return(_a0 error) *MockManager_Engage_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockManager_Engage_Call) RunAndReturn(run func(context.Context, string, cluster.Cluster) error) *MockManager_Engage_Call { + _c.Call.Return(run) + return _c +} + +// GetCluster provides a mock function with given fields: ctx, clusterName +func (_m *MockManager) GetCluster(ctx context.Context, clusterName string) (cluster.Cluster, error) { + ret := _m.Called(ctx, clusterName) + + if len(ret) == 0 { + panic("no return value specified for GetCluster") + } + + var r0 cluster.Cluster + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (cluster.Cluster, error)); ok { + return rf(ctx, clusterName) + } + if rf, ok := ret.Get(0).(func(context.Context, string) cluster.Cluster); ok { + r0 = rf(ctx, clusterName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(cluster.Cluster) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, clusterName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockManager_GetCluster_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCluster' +type MockManager_GetCluster_Call struct { + *mock.Call +} + +// GetCluster is a helper method to define mock.On call +// - ctx context.Context +// - clusterName string +func (_e *MockManager_Expecter) GetCluster(ctx interface{}, clusterName interface{}) *MockManager_GetCluster_Call { + return &MockManager_GetCluster_Call{Call: _e.mock.On("GetCluster", ctx, clusterName)} +} + +func (_c *MockManager_GetCluster_Call) Run(run func(ctx context.Context, clusterName string)) *MockManager_GetCluster_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockManager_GetCluster_Call) Return(_a0 cluster.Cluster, _a1 error) *MockManager_GetCluster_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockManager_GetCluster_Call) RunAndReturn(run func(context.Context, string) (cluster.Cluster, error)) *MockManager_GetCluster_Call { + _c.Call.Return(run) + return _c +} + +// GetControllerOptions provides a mock function with no fields +func (_m *MockManager) GetControllerOptions() config.Controller { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetControllerOptions") + } + + var r0 config.Controller + if rf, ok := ret.Get(0).(func() config.Controller); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(config.Controller) + } + + return r0 +} + +// MockManager_GetControllerOptions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetControllerOptions' +type MockManager_GetControllerOptions_Call struct { + *mock.Call +} + +// GetControllerOptions is a helper method to define mock.On call +func (_e *MockManager_Expecter) GetControllerOptions() *MockManager_GetControllerOptions_Call { + return &MockManager_GetControllerOptions_Call{Call: _e.mock.On("GetControllerOptions")} +} + +func (_c *MockManager_GetControllerOptions_Call) Run(run func()) *MockManager_GetControllerOptions_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockManager_GetControllerOptions_Call) Return(_a0 config.Controller) *MockManager_GetControllerOptions_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockManager_GetControllerOptions_Call) RunAndReturn(run func() config.Controller) *MockManager_GetControllerOptions_Call { + _c.Call.Return(run) + return _c +} + +// GetFieldIndexer provides a mock function with no fields +func (_m *MockManager) GetFieldIndexer() client.FieldIndexer { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetFieldIndexer") + } + + var r0 client.FieldIndexer + if rf, ok := ret.Get(0).(func() client.FieldIndexer); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(client.FieldIndexer) + } + } + + return r0 +} + +// MockManager_GetFieldIndexer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFieldIndexer' +type MockManager_GetFieldIndexer_Call struct { + *mock.Call +} + +// GetFieldIndexer is a helper method to define mock.On call +func (_e *MockManager_Expecter) GetFieldIndexer() *MockManager_GetFieldIndexer_Call { + return &MockManager_GetFieldIndexer_Call{Call: _e.mock.On("GetFieldIndexer")} +} + +func (_c *MockManager_GetFieldIndexer_Call) Run(run func()) *MockManager_GetFieldIndexer_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockManager_GetFieldIndexer_Call) Return(_a0 client.FieldIndexer) *MockManager_GetFieldIndexer_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockManager_GetFieldIndexer_Call) RunAndReturn(run func() client.FieldIndexer) *MockManager_GetFieldIndexer_Call { + _c.Call.Return(run) + return _c +} + +// GetLocalManager provides a mock function with no fields +func (_m *MockManager) GetLocalManager() pkgmanager.Manager { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetLocalManager") + } + + var r0 pkgmanager.Manager + if rf, ok := ret.Get(0).(func() pkgmanager.Manager); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(pkgmanager.Manager) + } + } + + return r0 +} + +// MockManager_GetLocalManager_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLocalManager' +type MockManager_GetLocalManager_Call struct { + *mock.Call +} + +// GetLocalManager is a helper method to define mock.On call +func (_e *MockManager_Expecter) GetLocalManager() *MockManager_GetLocalManager_Call { + return &MockManager_GetLocalManager_Call{Call: _e.mock.On("GetLocalManager")} +} + +func (_c *MockManager_GetLocalManager_Call) Run(run func()) *MockManager_GetLocalManager_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockManager_GetLocalManager_Call) Return(_a0 pkgmanager.Manager) *MockManager_GetLocalManager_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockManager_GetLocalManager_Call) RunAndReturn(run func() pkgmanager.Manager) *MockManager_GetLocalManager_Call { + _c.Call.Return(run) + return _c +} + +// GetLogger provides a mock function with no fields +func (_m *MockManager) GetLogger() logr.Logger { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetLogger") + } + + var r0 logr.Logger + if rf, ok := ret.Get(0).(func() logr.Logger); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(logr.Logger) + } + + return r0 +} + +// MockManager_GetLogger_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLogger' +type MockManager_GetLogger_Call struct { + *mock.Call +} + +// GetLogger is a helper method to define mock.On call +func (_e *MockManager_Expecter) GetLogger() *MockManager_GetLogger_Call { + return &MockManager_GetLogger_Call{Call: _e.mock.On("GetLogger")} +} + +func (_c *MockManager_GetLogger_Call) Run(run func()) *MockManager_GetLogger_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockManager_GetLogger_Call) Return(_a0 logr.Logger) *MockManager_GetLogger_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockManager_GetLogger_Call) RunAndReturn(run func() logr.Logger) *MockManager_GetLogger_Call { + _c.Call.Return(run) + return _c +} + +// GetManager provides a mock function with given fields: ctx, clusterName +func (_m *MockManager) GetManager(ctx context.Context, clusterName string) (pkgmanager.Manager, error) { + ret := _m.Called(ctx, clusterName) + + if len(ret) == 0 { + panic("no return value specified for GetManager") + } + + var r0 pkgmanager.Manager + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (pkgmanager.Manager, error)); ok { + return rf(ctx, clusterName) + } + if rf, ok := ret.Get(0).(func(context.Context, string) pkgmanager.Manager); ok { + r0 = rf(ctx, clusterName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(pkgmanager.Manager) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, clusterName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockManager_GetManager_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetManager' +type MockManager_GetManager_Call struct { + *mock.Call +} + +// GetManager is a helper method to define mock.On call +// - ctx context.Context +// - clusterName string +func (_e *MockManager_Expecter) GetManager(ctx interface{}, clusterName interface{}) *MockManager_GetManager_Call { + return &MockManager_GetManager_Call{Call: _e.mock.On("GetManager", ctx, clusterName)} +} + +func (_c *MockManager_GetManager_Call) Run(run func(ctx context.Context, clusterName string)) *MockManager_GetManager_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockManager_GetManager_Call) Return(_a0 pkgmanager.Manager, _a1 error) *MockManager_GetManager_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockManager_GetManager_Call) RunAndReturn(run func(context.Context, string) (pkgmanager.Manager, error)) *MockManager_GetManager_Call { + _c.Call.Return(run) + return _c +} + +// GetProvider provides a mock function with no fields +func (_m *MockManager) GetProvider() multicluster.Provider { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetProvider") + } + + var r0 multicluster.Provider + if rf, ok := ret.Get(0).(func() multicluster.Provider); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(multicluster.Provider) + } + } + + return r0 +} + +// MockManager_GetProvider_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProvider' +type MockManager_GetProvider_Call struct { + *mock.Call +} + +// GetProvider is a helper method to define mock.On call +func (_e *MockManager_Expecter) GetProvider() *MockManager_GetProvider_Call { + return &MockManager_GetProvider_Call{Call: _e.mock.On("GetProvider")} +} + +func (_c *MockManager_GetProvider_Call) Run(run func()) *MockManager_GetProvider_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockManager_GetProvider_Call) Return(_a0 multicluster.Provider) *MockManager_GetProvider_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockManager_GetProvider_Call) RunAndReturn(run func() multicluster.Provider) *MockManager_GetProvider_Call { + _c.Call.Return(run) + return _c +} + +// GetWebhookServer provides a mock function with no fields +func (_m *MockManager) GetWebhookServer() webhook.Server { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetWebhookServer") + } + + var r0 webhook.Server + if rf, ok := ret.Get(0).(func() webhook.Server); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(webhook.Server) + } + } + + return r0 +} + +// MockManager_GetWebhookServer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWebhookServer' +type MockManager_GetWebhookServer_Call struct { + *mock.Call +} + +// GetWebhookServer is a helper method to define mock.On call +func (_e *MockManager_Expecter) GetWebhookServer() *MockManager_GetWebhookServer_Call { + return &MockManager_GetWebhookServer_Call{Call: _e.mock.On("GetWebhookServer")} +} + +func (_c *MockManager_GetWebhookServer_Call) Run(run func()) *MockManager_GetWebhookServer_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockManager_GetWebhookServer_Call) Return(_a0 webhook.Server) *MockManager_GetWebhookServer_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockManager_GetWebhookServer_Call) RunAndReturn(run func() webhook.Server) *MockManager_GetWebhookServer_Call { + _c.Call.Return(run) + return _c +} + +// Start provides a mock function with given fields: ctx +func (_m *MockManager) Start(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Start") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockManager_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' +type MockManager_Start_Call struct { + *mock.Call +} + +// Start is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockManager_Expecter) Start(ctx interface{}) *MockManager_Start_Call { + return &MockManager_Start_Call{Call: _e.mock.On("Start", ctx)} +} + +func (_c *MockManager_Start_Call) Run(run func(ctx context.Context)) *MockManager_Start_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockManager_Start_Call) Return(_a0 error) *MockManager_Start_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockManager_Start_Call) RunAndReturn(run func(context.Context) error) *MockManager_Start_Call { + _c.Call.Return(run) + return _c +} + +// NewMockManager creates a new instance of MockManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockManager(t interface { + mock.TestingT + Cleanup(func()) +}) *MockManager { + mock := &MockManager{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/subroutine/mocks/mock_OpenFGAServiceClient.go b/internal/subroutine/mocks/mock_OpenFGAServiceClient.go index f8cfe3a..830c4f0 100644 --- a/internal/subroutine/mocks/mock_OpenFGAServiceClient.go +++ b/internal/subroutine/mocks/mock_OpenFGAServiceClient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.0. DO NOT EDIT. +// Code generated by mockery v2.53.5. DO NOT EDIT. package mocks diff --git a/internal/subroutine/realm.go b/internal/subroutine/realm.go index 3e45dcd..4cb9944 100644 --- a/internal/subroutine/realm.go +++ b/internal/subroutine/realm.go @@ -10,7 +10,7 @@ import ( "text/template" kcpv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1" - lifecycleruntimeobject "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" + "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" lifecyclesubroutine "github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine" "github.com/platform-mesh/golang-commons/errors" "github.com/platform-mesh/golang-commons/logger" @@ -49,9 +49,11 @@ var _ lifecyclesubroutine.Subroutine = &realmSubroutine{} func (r *realmSubroutine) GetName() string { return "Realm" } -func (r *realmSubroutine) Finalizers() []string { return []string{} } +func (r *realmSubroutine) Finalizers(_ runtimeobject.RuntimeObject) []string { + return []string{} +} -func (r *realmSubroutine) Finalize(ctx context.Context, instance lifecycleruntimeobject.RuntimeObject) (reconcile.Result, errors.OperatorError) { +func (r *realmSubroutine) Finalize(ctx context.Context, instance runtimeobject.RuntimeObject) (reconcile.Result, errors.OperatorError) { log := logger.LoadLoggerFromContext(ctx) lc := instance.(*kcpv1alpha1.LogicalCluster) @@ -81,7 +83,7 @@ func (r *realmSubroutine) Finalize(ctx context.Context, instance lifecycleruntim return ctrl.Result{}, nil } -func (r *realmSubroutine) Process(ctx context.Context, instance lifecycleruntimeobject.RuntimeObject) (reconcile.Result, errors.OperatorError) { +func (r *realmSubroutine) Process(ctx context.Context, instance runtimeobject.RuntimeObject) (reconcile.Result, errors.OperatorError) { lc := instance.(*kcpv1alpha1.LogicalCluster) workspaceName := getWorkspaceName(lc) @@ -96,11 +98,9 @@ func (r *realmSubroutine) Process(ctx context.Context, instance lifecycleruntime "displayName": workspaceName, }, "client": map[string]any{ - "name": workspaceName, - "displayName": workspaceName, - "validRedirectUris": []string{ - fmt.Sprintf("https://%s.%s/callback*", workspaceName, r.baseDomain), - }, + "name": workspaceName, + "displayName": workspaceName, + "validRedirectUris": append(r.cfg.IDP.AdditionalRedirectURLs, fmt.Sprintf("https://%s.%s/callback*", workspaceName, r.baseDomain)), }, "organization": map[string]any{ "domain": "example.com", // TODO: change diff --git a/internal/subroutine/realm_test.go b/internal/subroutine/realm_test.go index 48831e6..e9f7686 100644 --- a/internal/subroutine/realm_test.go +++ b/internal/subroutine/realm_test.go @@ -184,6 +184,27 @@ func TestRealmSubroutine_ProcessAndFinalize(t *testing.T) { require.Equal(t, ctrl.Result{}, res) }) + // New: success create with SMTP config + t.Run("success create with SMTP config", func(t *testing.T) { + t.Parallel() + clientMock := newClientMock(t, func(m *mocks.MockClient) { + m.EXPECT().Patch(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Twice() + }) + + cfg := &config.Config{} + cfg.IDP.SMTPServer = "smtp.example.com" + cfg.IDP.SMTPPort = 587 + cfg.IDP.FromAddress = "noreply@example.com" + cfg.IDP.SSL = false + cfg.IDP.StartTLS = true + rs := NewRealmSubroutine(clientMock, cfg, baseDomain) + lc := &kcpv1alpha1.LogicalCluster{} + lc.Annotations = map[string]string{"kcp.io/path": "root:orgs:test"} + res, opErr := rs.Process(context.Background(), lc) + require.Nil(t, opErr) + require.Equal(t, ctrl.Result{}, res) + }) + t.Run("oci repository apply fails", func(t *testing.T) { t.Parallel() clientMock := newClientMock(t, func(m *mocks.MockClient) { @@ -200,6 +221,16 @@ func TestRealmSubroutine_ProcessAndFinalize(t *testing.T) { require.Equal(t, ctrl.Result{}, res) }) + // New: Finalize missing workspace annotation + t.Run("missing workspace annotation in Finalize", func(t *testing.T) { + clientMock := newClientMock(t, nil) + rs := NewRealmSubroutine(clientMock, &config.Config{}, baseDomain) + lc := &kcpv1alpha1.LogicalCluster{} + res, opErr := rs.Finalize(context.Background(), lc) + require.NotNil(t, opErr) + require.Equal(t, ctrl.Result{}, res) + }) + t.Run("missing workspace annotation", func(t *testing.T) { t.Parallel() clientMock := newClientMock(t, nil) diff --git a/internal/subroutine/remove_initializer.go b/internal/subroutine/remove_initializer.go new file mode 100644 index 0000000..699dca2 --- /dev/null +++ b/internal/subroutine/remove_initializer.go @@ -0,0 +1,70 @@ +package subroutine + +import ( + "context" + "fmt" + "slices" + + "github.com/kcp-dev/kcp/sdk/apis/cache/initialization" + kcpv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1" + "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" + "github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine" + "github.com/platform-mesh/golang-commons/errors" + "github.com/rs/zerolog/log" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" +) + +type removeInitializer struct { + initializerName string + mgr mcmanager.Manager +} + +// Finalize implements subroutine.Subroutine. +func (r *removeInitializer) Finalize(ctx context.Context, instance runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { + return ctrl.Result{}, nil +} + +// Finalizers implements subroutine.Subroutine. +func (r *removeInitializer) Finalizers(_ runtimeobject.RuntimeObject) []string { return []string{} } + +// GetName implements subroutine.Subroutine. +func (r *removeInitializer) GetName() string { return "RemoveInitializer" } + +// Process implements subroutine.Subroutine. +func (r *removeInitializer) Process(ctx context.Context, instance runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { + lc := instance.(*kcpv1alpha1.LogicalCluster) + + initializer := kcpv1alpha1.LogicalClusterInitializer(r.initializerName) + + cluster, err := r.mgr.ClusterFromContext(ctx) + if err != nil { + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("unable to get cluster from context: %w", err), true, false) + } + + if !slices.Contains(lc.Status.Initializers, initializer) { + log.Info().Msg("Initializer already absent, skipping patch") + return ctrl.Result{}, nil + } + + patch := client.MergeFrom(lc.DeepCopy()) + + lc.Status.Initializers = initialization.EnsureInitializerAbsent(initializer, lc.Status.Initializers) + if err := cluster.GetClient().Status().Patch(ctx, lc, patch); err != nil { + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("unable to patch out initializers: %w", err), true, true) + } + + log.Info().Msg(fmt.Sprintf("Removed initializer from LogicalCluster status, name %s,uuid %s", lc.Name, lc.UID)) + + return ctrl.Result{}, nil +} + +func NewRemoveInitializer(mgr mcmanager.Manager, initializerName string) *removeInitializer { + return &removeInitializer{ + initializerName: initializerName, + mgr: mgr, + } +} + +var _ subroutine.Subroutine = &removeInitializer{} diff --git a/internal/subroutine/remove_initializer_test.go b/internal/subroutine/remove_initializer_test.go new file mode 100644 index 0000000..25e4b72 --- /dev/null +++ b/internal/subroutine/remove_initializer_test.go @@ -0,0 +1,120 @@ +package subroutine_test + +import ( + "context" + "testing" + + kcpv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1" + "github.com/platform-mesh/security-operator/internal/subroutine" + "github.com/platform-mesh/security-operator/internal/subroutine/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// fakeStatusWriter implements client.SubResourceWriter to intercept Status().Patch calls +type fakeStatusWriter struct { + t *testing.T + expectClear kcpv1alpha1.LogicalClusterInitializer + err error +} + +func (f *fakeStatusWriter) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error { + return nil +} + +func (f *fakeStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return nil +} + +func (f *fakeStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { + lc := obj.(*kcpv1alpha1.LogicalCluster) + // Ensure initializer was removed before patch + for _, init := range lc.Status.Initializers { + if init == f.expectClear { + f.t.Fatalf("initializer %q should have been removed prior to Patch", string(init)) + } + } + return f.err +} + +func TestRemoveInitializer_Process(t *testing.T) { + const initializerName = "foo.initializer.kcp.dev" + + t.Run("skips when initializer is absent", func(t *testing.T) { + mgr := mocks.NewMockManager(t) + cluster := mocks.NewMockCluster(t) + mgr.EXPECT().ClusterFromContext(mock.Anything).Return(cluster, nil) + + lc := &kcpv1alpha1.LogicalCluster{} + lc.Status.Initializers = []kcpv1alpha1.LogicalClusterInitializer{"other.initializer"} + + r := subroutine.NewRemoveInitializer(mgr, initializerName) + _, err := r.Process(context.Background(), lc) + assert.Nil(t, err) + }) + + t.Run("removes initializer and patches status", func(t *testing.T) { + mgr := mocks.NewMockManager(t) + cluster := mocks.NewMockCluster(t) + k8s := mocks.NewMockClient(t) + + mgr.EXPECT().ClusterFromContext(mock.Anything).Return(cluster, nil) + cluster.EXPECT().GetClient().Return(k8s) + k8s.EXPECT().Status().Return(&fakeStatusWriter{t: t, expectClear: kcpv1alpha1.LogicalClusterInitializer(initializerName), err: nil}) + + lc := &kcpv1alpha1.LogicalCluster{} + lc.Status.Initializers = []kcpv1alpha1.LogicalClusterInitializer{ + kcpv1alpha1.LogicalClusterInitializer(initializerName), + "another.initializer", + } + + r := subroutine.NewRemoveInitializer(mgr, initializerName) + _, err := r.Process(context.Background(), lc) + assert.Nil(t, err) + // ensure it's removed in in-memory object as well + for _, init := range lc.Status.Initializers { + assert.NotEqual(t, initializerName, string(init)) + } + }) + + t.Run("returns error when status patch fails", func(t *testing.T) { + mgr := mocks.NewMockManager(t) + cluster := mocks.NewMockCluster(t) + k8s := mocks.NewMockClient(t) + + mgr.EXPECT().ClusterFromContext(mock.Anything).Return(cluster, nil) + cluster.EXPECT().GetClient().Return(k8s) + k8s.EXPECT().Status().Return(&fakeStatusWriter{t: t, expectClear: kcpv1alpha1.LogicalClusterInitializer(initializerName), err: assert.AnError}) + + lc := &kcpv1alpha1.LogicalCluster{} + lc.Status.Initializers = []kcpv1alpha1.LogicalClusterInitializer{ + kcpv1alpha1.LogicalClusterInitializer(initializerName), + } + + r := subroutine.NewRemoveInitializer(mgr, initializerName) + _, err := r.Process(context.Background(), lc) + assert.NotNil(t, err) + }) +} + +func TestRemoveInitializer_Misc(t *testing.T) { + mgr := mocks.NewMockManager(t) + r := subroutine.NewRemoveInitializer(mgr, "foo.initializer.kcp.dev") + + assert.Equal(t, "RemoveInitializer", r.GetName()) + assert.Equal(t, []string{}, r.Finalizers(nil)) + + _, err := r.Finalize(context.Background(), &kcpv1alpha1.LogicalCluster{}) + assert.Nil(t, err) +} + +func TestRemoveInitializer_ManagerError(t *testing.T) { + mgr := mocks.NewMockManager(t) + // Simulate error fetching cluster from context + mgr.EXPECT().ClusterFromContext(mock.Anything).Return(nil, assert.AnError) + + r := subroutine.NewRemoveInitializer(mgr, "foo.initializer.kcp.dev") + _, err := r.Process(context.Background(), &kcpv1alpha1.LogicalCluster{}) + assert.NotNil(t, err) +} diff --git a/internal/subroutine/store.go b/internal/subroutine/store.go index 758dd7a..1597696 100644 --- a/internal/subroutine/store.go +++ b/internal/subroutine/store.go @@ -2,33 +2,32 @@ package subroutine import ( "context" + "fmt" "slices" openfgav1 "github.com/openfga/api/proto/openfga/v1" - lifecycleruntimeobject "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" + "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" lifecyclesubroutine "github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine" "github.com/platform-mesh/golang-commons/errors" "github.com/platform-mesh/golang-commons/logger" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" "github.com/platform-mesh/security-operator/api/v1alpha1" ) type storeSubroutine struct { - fga openfgav1.OpenFGAServiceClient - k8s client.Client - lcClientFunc NewLogicalClusterClientFunc + fga openfgav1.OpenFGAServiceClient + mgr mcmanager.Manager } -func NewStoreSubroutine(fga openfgav1.OpenFGAServiceClient, k8s client.Client, lcClientFunc NewLogicalClusterClientFunc) *storeSubroutine { +func NewStoreSubroutine(fga openfgav1.OpenFGAServiceClient, mgr mcmanager.Manager) *storeSubroutine { return &storeSubroutine{ - fga: fga, - k8s: k8s, - lcClientFunc: lcClientFunc, + fga: fga, + mgr: mgr, } } @@ -36,9 +35,11 @@ var _ lifecyclesubroutine.Subroutine = &storeSubroutine{} func (s *storeSubroutine) GetName() string { return "Store" } -func (s *storeSubroutine) Finalizers() []string { return []string{"core.platform-mesh.io/fga-store"} } +func (s *storeSubroutine) Finalizers(_ runtimeobject.RuntimeObject) []string { + return []string{"core.platform-mesh.io/fga-store"} +} -func (s *storeSubroutine) Finalize(ctx context.Context, instance lifecycleruntimeobject.RuntimeObject) (reconcile.Result, errors.OperatorError) { +func (s *storeSubroutine) Finalize(ctx context.Context, instance runtimeobject.RuntimeObject) (reconcile.Result, errors.OperatorError) { log := logger.LoadLoggerFromContext(ctx) store := instance.(*v1alpha1.Store) @@ -46,7 +47,12 @@ func (s *storeSubroutine) Finalize(ctx context.Context, instance lifecycleruntim return ctrl.Result{}, nil } - authorizationModels, err := getRelatedAuthorizationModels(ctx, s.k8s, store, s.lcClientFunc) + cluster, err := s.mgr.ClusterFromContext(ctx) + if err != nil { + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("unable to get cluster from context: %w", err), true, false) + } + + authorizationModels, err := getRelatedAuthorizationModels(ctx, cluster.GetClient(), store) if err != nil { return ctrl.Result{}, errors.NewOperatorError(err, true, false) } @@ -66,7 +72,7 @@ func (s *storeSubroutine) Finalize(ctx context.Context, instance lifecycleruntim return ctrl.Result{}, nil } -func (s *storeSubroutine) Process(ctx context.Context, instance lifecycleruntimeobject.RuntimeObject) (reconcile.Result, errors.OperatorError) { +func (s *storeSubroutine) Process(ctx context.Context, instance runtimeobject.RuntimeObject) (reconcile.Result, errors.OperatorError) { log := logger.LoadLoggerFromContext(ctx) store := instance.(*v1alpha1.Store) diff --git a/internal/subroutine/store_test.go b/internal/subroutine/store_test.go index 2a7bd8c..643160a 100644 --- a/internal/subroutine/store_test.go +++ b/internal/subroutine/store_test.go @@ -16,17 +16,17 @@ import ( "google.golang.org/grpc/status" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/kontext" + mccontext "sigs.k8s.io/multicluster-runtime/pkg/context" ) func TestGetName(t *testing.T) { - subroutine := subroutine.NewStoreSubroutine(nil, nil, nil) + subroutine := subroutine.NewStoreSubroutine(nil, nil) assert.Equal(t, "Store", subroutine.GetName()) } func TestFinalizers(t *testing.T) { - subroutine := subroutine.NewStoreSubroutine(nil, nil, nil) - assert.Equal(t, []string{"core.platform-mesh.io/fga-store"}, subroutine.Finalizers()) + subroutine := subroutine.NewStoreSubroutine(nil, nil) + assert.Equal(t, []string{"core.platform-mesh.io/fga-store"}, subroutine.Finalizers(nil)) } func TestProcess(t *testing.T) { @@ -35,6 +35,7 @@ func TestProcess(t *testing.T) { store *v1alpha1.Store fgaMocks func(*mocks.MockOpenFGAServiceClient) k8sMocks func(*mocks.MockClient) + mgrMocks func(*mocks.MockManager) expectError bool }{ { @@ -129,14 +130,8 @@ func TestProcess(t *testing.T) { test.fgaMocks(fga) } - k8s := mocks.NewMockClient(t) - if test.k8sMocks != nil { - test.k8sMocks(k8s) - } - - subroutine := subroutine.NewStoreSubroutine(fga, k8s, func(clusterKey logicalcluster.Name) (client.Client, error) { - return k8s, nil - }) + manager := mocks.NewMockManager(t) + subroutine := subroutine.NewStoreSubroutine(fga, manager) _, err := subroutine.Process(context.Background(), test.store) if test.expectError { @@ -155,6 +150,7 @@ func TestFinalize(t *testing.T) { store *v1alpha1.Store fgaMocks func(*mocks.MockOpenFGAServiceClient) k8sMocks func(*mocks.MockClient) + mgrMocks func(*mocks.MockManager) expectError bool }{ { @@ -176,8 +172,7 @@ func TestFinalize(t *testing.T) { }, }, k8sMocks: func(k8s *mocks.MockClient) { - mockLogicalClusterGet(k8s) - k8s.EXPECT().List(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, ol client.ObjectList, lo ...client.ListOption) error { + k8s.EXPECT().List(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, ol client.ObjectList, lo ...client.ListOption) error { if list, ok := ol.(*v1alpha1.AuthorizationModelList); ok { list.Items = []v1alpha1.AuthorizationModel{ { @@ -206,8 +201,7 @@ func TestFinalize(t *testing.T) { }, }, k8sMocks: func(k8s *mocks.MockClient) { - mockLogicalClusterGet(k8s) - k8s.EXPECT().List(mock.Anything, mock.Anything, mock.Anything).Return(errors.New("error")) + k8s.EXPECT().List(mock.Anything, mock.Anything).Return(errors.New("error")) }, expectError: true, }, @@ -222,8 +216,7 @@ func TestFinalize(t *testing.T) { }, }, k8sMocks: func(k8s *mocks.MockClient) { - mockLogicalClusterGet(k8s) - k8s.EXPECT().List(mock.Anything, mock.Anything, mock.Anything).Return(nil) + k8s.EXPECT().List(mock.Anything, mock.Anything).Return(nil) }, fgaMocks: func(fga *mocks.MockOpenFGAServiceClient) { fga.EXPECT().DeleteStore(mock.Anything, &openfgav1.DeleteStoreRequest{StoreId: "id"}).Return(nil, nil) @@ -240,8 +233,7 @@ func TestFinalize(t *testing.T) { }, }, k8sMocks: func(k8s *mocks.MockClient) { - mockLogicalClusterGet(k8s) - k8s.EXPECT().List(mock.Anything, mock.Anything, mock.Anything).Return(nil) + k8s.EXPECT().List(mock.Anything, mock.Anything).Return(nil) }, fgaMocks: func(fga *mocks.MockOpenFGAServiceClient) { fga.EXPECT().DeleteStore(mock.Anything, &openfgav1.DeleteStoreRequest{StoreId: "id"}).Return(nil, status.Error(codes.Code(openfgav1.NotFoundErrorCode_store_id_not_found), "not found")) @@ -258,8 +250,7 @@ func TestFinalize(t *testing.T) { }, }, k8sMocks: func(k8s *mocks.MockClient) { - mockLogicalClusterGet(k8s) - k8s.EXPECT().List(mock.Anything, mock.Anything, mock.Anything).Return(nil) + k8s.EXPECT().List(mock.Anything, mock.Anything).Return(nil) }, fgaMocks: func(fga *mocks.MockOpenFGAServiceClient) { fga.EXPECT().DeleteStore(mock.Anything, &openfgav1.DeleteStoreRequest{StoreId: "id"}).Return(nil, errors.New("error")) @@ -275,16 +266,21 @@ func TestFinalize(t *testing.T) { test.fgaMocks(fga) } - k8s := mocks.NewMockClient(t) - if test.k8sMocks != nil { - test.k8sMocks(k8s) - } + manager := mocks.NewMockManager(t) + subroutine := subroutine.NewStoreSubroutine(fga, manager) - subroutine := subroutine.NewStoreSubroutine(fga, k8s, func(clusterKey logicalcluster.Name) (client.Client, error) { - return k8s, nil - }) + // Only wire cluster/client expectations when Finalize will actually query k8s (i.e., StoreID is set) + if test.store.Status.StoreID != "" { + cluster := mocks.NewMockCluster(t) + client := mocks.NewMockClient(t) + if test.k8sMocks != nil { + test.k8sMocks(client) + } + manager.EXPECT().ClusterFromContext(mock.Anything).Return(cluster, nil) + cluster.EXPECT().GetClient().Return(client) + } - ctx := kontext.WithCluster(context.Background(), logicalcluster.Name("a")) + ctx := mccontext.WithCluster(context.Background(), string(logicalcluster.Name("path"))) _, err := subroutine.Finalize(ctx, test.store) if test.expectError { diff --git a/internal/subroutine/tuples.go b/internal/subroutine/tuples.go index f243a17..e04d658 100644 --- a/internal/subroutine/tuples.go +++ b/internal/subroutine/tuples.go @@ -2,12 +2,13 @@ package subroutine import ( "context" + "fmt" "slices" kcpcorev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1" "github.com/kcp-dev/logicalcluster/v3" openfgav1 "github.com/openfga/api/proto/openfga/v1" - lifecycleruntimeobject "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" + "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" lifecyclesubroutine "github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine" "github.com/platform-mesh/golang-commons/errors" "github.com/platform-mesh/golang-commons/fga/helpers" @@ -16,17 +17,17 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/kontext" + mccontext "sigs.k8s.io/multicluster-runtime/pkg/context" + mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" ) type tupleSubroutine struct { - fga openfgav1.OpenFGAServiceClient - k8s client.Client - lcClientFunc NewLogicalClusterClientFunc + fga openfgav1.OpenFGAServiceClient + mgr mcmanager.Manager } // Finalize implements lifecycle.Subroutine. -func (t *tupleSubroutine) Finalize(ctx context.Context, instance lifecycleruntimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { +func (t *tupleSubroutine) Finalize(ctx context.Context, instance runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { log := logger.LoadLoggerFromContext(ctx) var storeID string @@ -39,23 +40,23 @@ func (t *tupleSubroutine) Finalize(ctx context.Context, instance lifecycleruntim authorizationModelID = obj.Status.AuthorizationModelID managedTuples = obj.Status.ManagedTuples case *v1alpha1.AuthorizationModel: - managedTuples = obj.Status.ManagedTuples - - lcClient, err := t.lcClientFunc(logicalcluster.Name(obj.Spec.StoreRef.Path)) + cluster, err := t.mgr.ClusterFromContext(ctx) if err != nil { - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("unable to get cluster from context: %w", err), true, false) } + managedTuples = obj.Status.ManagedTuples + var lc kcpcorev1alpha1.LogicalCluster - err = lcClient.Get(ctx, client.ObjectKey{Name: "cluster"}, &lc) + err = cluster.GetClient().Get(ctx, client.ObjectKey{Name: "cluster"}, &lc) if err != nil { return ctrl.Result{}, errors.NewOperatorError(err, true, true) } - storeCtx := kontext.WithCluster(ctx, logicalcluster.Name(lc.Annotations[logicalcluster.AnnotationKey])) + storeCtx := mccontext.WithCluster(ctx, string(logicalcluster.Name(lc.Annotations[logicalcluster.AnnotationKey]))) var store v1alpha1.Store - err = t.k8s.Get(storeCtx, types.NamespacedName{ + err = cluster.GetClient().Get(storeCtx, types.NamespacedName{ Name: obj.Spec.StoreRef.Name, }, &store) if err != nil { @@ -100,13 +101,15 @@ func (t *tupleSubroutine) Finalize(ctx context.Context, instance lifecycleruntim } // Finalizers implements lifecycle.Subroutine. -func (t *tupleSubroutine) Finalizers() []string { return []string{"core.platform-mesh.io/fga-tuples"} } +func (t *tupleSubroutine) Finalizers(_ runtimeobject.RuntimeObject) []string { + return []string{"core.platform-mesh.io/fga-tuples"} +} // GetName implements lifecycle.Subroutine. func (t *tupleSubroutine) GetName() string { return "TupleSubroutine" } // Process implements lifecycle.Subroutine. -func (t *tupleSubroutine) Process(ctx context.Context, instance lifecycleruntimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { +func (t *tupleSubroutine) Process(ctx context.Context, instance runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { log := logger.LoadLoggerFromContext(ctx) var storeID string @@ -122,24 +125,29 @@ func (t *tupleSubroutine) Process(ctx context.Context, instance lifecycleruntime specTuples = obj.Spec.Tuples managedTuples = obj.Status.ManagedTuples case *v1alpha1.AuthorizationModel: + cluster, err := t.mgr.ClusterFromContext(ctx) + if err != nil { + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("unable to get cluster from context: %w", err), true, false) + } + specTuples = obj.Spec.Tuples managedTuples = obj.Status.ManagedTuples - lcClient, err := t.lcClientFunc(logicalcluster.Name(obj.Spec.StoreRef.Path)) + var lc kcpcorev1alpha1.LogicalCluster + err = cluster.GetClient().Get(ctx, client.ObjectKey{Name: "cluster"}, &lc) if err != nil { return ctrl.Result{}, errors.NewOperatorError(err, true, true) } - var lc kcpcorev1alpha1.LogicalCluster - err = lcClient.Get(ctx, client.ObjectKey{Name: "cluster"}, &lc) + storeCtx := mccontext.WithCluster(ctx, string(logicalcluster.Name(lc.Annotations[logicalcluster.AnnotationKey]))) + + storeCluster, err := t.mgr.GetCluster(ctx,obj.Spec.StoreRef.Path) if err != nil { - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("unable to get store cluster: %w", err), true, false) } - storeCtx := kontext.WithCluster(ctx, logicalcluster.Name(lc.Annotations[logicalcluster.AnnotationKey])) - var store v1alpha1.Store - err = t.k8s.Get(storeCtx, types.NamespacedName{ + err = storeCluster.GetClient().Get(storeCtx, types.NamespacedName{ Name: obj.Spec.StoreRef.Name, }, &store) if err != nil { // coverage-ignore @@ -213,11 +221,10 @@ func (t *tupleSubroutine) Process(ctx context.Context, instance lifecycleruntime return ctrl.Result{}, nil } -func NewTupleSubroutine(fga openfgav1.OpenFGAServiceClient, k8s client.Client, lcClientFunc NewLogicalClusterClientFunc) *tupleSubroutine { +func NewTupleSubroutine(fga openfgav1.OpenFGAServiceClient, mgr mcmanager.Manager) *tupleSubroutine { return &tupleSubroutine{ - fga: fga, - k8s: k8s, - lcClientFunc: lcClientFunc, + fga: fga, + mgr: mgr, } } diff --git a/internal/subroutine/tuples_test.go b/internal/subroutine/tuples_test.go index f243fbd..4ade6c4 100644 --- a/internal/subroutine/tuples_test.go +++ b/internal/subroutine/tuples_test.go @@ -14,17 +14,17 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/kontext" + mccontext "sigs.k8s.io/multicluster-runtime/pkg/context" ) func TestTupleGetName(t *testing.T) { - subroutine := subroutine.NewTupleSubroutine(nil, nil, nil) + subroutine := subroutine.NewTupleSubroutine(nil, nil) assert.Equal(t, "TupleSubroutine", subroutine.GetName()) } func TestTupleFinalizers(t *testing.T) { - subroutine := subroutine.NewTupleSubroutine(nil, nil, nil) - assert.Equal(t, []string{"core.platform-mesh.io/fga-tuples"}, subroutine.Finalizers()) + subroutine := subroutine.NewTupleSubroutine(nil, nil) + assert.Equal(t, []string{"core.platform-mesh.io/fga-tuples"}, subroutine.Finalizers(nil)) } func TestTupleProcessWithStore(t *testing.T) { @@ -33,6 +33,7 @@ func TestTupleProcessWithStore(t *testing.T) { store *v1alpha1.Store fgaMocks func(*mocks.MockOpenFGAServiceClient) k8sMocks func(*mocks.MockClient) + mgrMocks func(*mocks.MockManager) expectError bool }{ { @@ -162,12 +163,12 @@ func TestTupleProcessWithStore(t *testing.T) { test.fgaMocks(fga) } - k8s := mocks.NewMockClient(t) - if test.k8sMocks != nil { - test.k8sMocks(k8s) + manager := mocks.NewMockManager(t) + if test.mgrMocks != nil { + test.mgrMocks(manager) } - subroutine := subroutine.NewTupleSubroutine(fga, k8s, nil) + subroutine := subroutine.NewTupleSubroutine(fga, manager) _, err := subroutine.Process(context.Background(), test.store) if test.expectError { @@ -187,6 +188,7 @@ func TestTupleProcessWithAuthorizationModel(t *testing.T) { store *v1alpha1.AuthorizationModel fgaMocks func(*mocks.MockOpenFGAServiceClient) k8sMocks func(*mocks.MockClient) + mgrMocks func(*mocks.MockManager) expectError bool }{ { @@ -300,16 +302,19 @@ func TestTupleProcessWithAuthorizationModel(t *testing.T) { test.fgaMocks(fga) } - k8s := mocks.NewMockClient(t) + manager := mocks.NewMockManager(t) + cluster := mocks.NewMockCluster(t) + k8sClient := mocks.NewMockClient(t) if test.k8sMocks != nil { - test.k8sMocks(k8s) + test.k8sMocks(k8sClient) + manager.EXPECT().ClusterFromContext(mock.Anything).Return(cluster, nil) + manager.EXPECT().GetCluster(mock.Anything, mock.Anything).Return(cluster, nil).Maybe() + cluster.EXPECT().GetClient().Return(k8sClient).Maybe() } - subroutine := subroutine.NewTupleSubroutine(fga, k8s, func(clusterKey logicalcluster.Name) (client.Client, error) { - return k8s, nil - }) + subroutine := subroutine.NewTupleSubroutine(fga, manager) - ctx := kontext.WithCluster(context.Background(), logicalcluster.Name("a")) + ctx := mccontext.WithCluster(context.Background(), string(logicalcluster.Name("a"))) _, err := subroutine.Process(ctx, test.store) if test.expectError { @@ -329,6 +334,7 @@ func TestTupleFinalizationWithAuthorizationModel(t *testing.T) { store *v1alpha1.AuthorizationModel fgaMocks func(*mocks.MockOpenFGAServiceClient) k8sMocks func(*mocks.MockClient) + mgrMocks func(*mocks.MockManager) expectError bool }{ { @@ -375,16 +381,19 @@ func TestTupleFinalizationWithAuthorizationModel(t *testing.T) { test.fgaMocks(fga) } - k8s := mocks.NewMockClient(t) + manager := mocks.NewMockManager(t) + cluster := mocks.NewMockCluster(t) + k8sClient := mocks.NewMockClient(t) if test.k8sMocks != nil { - test.k8sMocks(k8s) + test.k8sMocks(k8sClient) + manager.EXPECT().ClusterFromContext(mock.Anything).Return(cluster, nil) + manager.EXPECT().GetCluster(mock.Anything, mock.Anything).Return(cluster, nil).Maybe() + cluster.EXPECT().GetClient().Return(k8sClient).Maybe() } - subroutine := subroutine.NewTupleSubroutine(fga, k8s, func(clusterKey logicalcluster.Name) (client.Client, error) { - return k8s, nil - }) + subroutine := subroutine.NewTupleSubroutine(fga, manager) - ctx := kontext.WithCluster(context.Background(), logicalcluster.Name("a")) + ctx := mccontext.WithCluster(context.Background(), string(logicalcluster.Name("a"))) _, err := subroutine.Finalize(ctx, test.store) if test.expectError { @@ -404,6 +413,7 @@ func TestTupleFinalizationWithStore(t *testing.T) { store *v1alpha1.Store fgaMocks func(*mocks.MockOpenFGAServiceClient) k8sMocks func(*mocks.MockClient) + mgrMocks func(*mocks.MockManager) expectError bool }{ { @@ -451,12 +461,12 @@ func TestTupleFinalizationWithStore(t *testing.T) { test.fgaMocks(fga) } - k8s := mocks.NewMockClient(t) - if test.k8sMocks != nil { - test.k8sMocks(k8s) + manager := mocks.NewMockManager(t) + if test.mgrMocks != nil { + test.mgrMocks(manager) } - subroutine := subroutine.NewTupleSubroutine(fga, k8s, nil) + subroutine := subroutine.NewTupleSubroutine(fga, manager) _, err := subroutine.Finalize(context.Background(), test.store) if test.expectError { diff --git a/internal/subroutine/worksapce_authorization.go b/internal/subroutine/worksapce_authorization.go index d268fe8..daa4eb1 100644 --- a/internal/subroutine/worksapce_authorization.go +++ b/internal/subroutine/worksapce_authorization.go @@ -3,11 +3,10 @@ package subroutine import ( "context" "fmt" - "time" kcpv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1" kcptenancyv1alphav1 "github.com/kcp-dev/kcp/sdk/apis/tenancy/v1alpha1" - lifecycleruntimeobject "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" + "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" lifecyclesubroutine "github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine" "github.com/platform-mesh/golang-commons/errors" "github.com/platform-mesh/security-operator/internal/config" @@ -38,13 +37,15 @@ var _ lifecyclesubroutine.Subroutine = &workspaceAuthSubroutine{} func (r *workspaceAuthSubroutine) GetName() string { return "workspaceAuthConfiguration" } -func (r *workspaceAuthSubroutine) Finalizers() []string { return []string{} } +func (r *workspaceAuthSubroutine) Finalizers(_ runtimeobject.RuntimeObject) []string { + return []string{} +} -func (r *workspaceAuthSubroutine) Finalize(ctx context.Context, instance lifecycleruntimeobject.RuntimeObject) (reconcile.Result, errors.OperatorError) { +func (r *workspaceAuthSubroutine) Finalize(ctx context.Context, instance runtimeobject.RuntimeObject) (reconcile.Result, errors.OperatorError) { return reconcile.Result{}, nil } -func (r *workspaceAuthSubroutine) Process(ctx context.Context, instance lifecycleruntimeobject.RuntimeObject) (reconcile.Result, errors.OperatorError) { +func (r *workspaceAuthSubroutine) Process(ctx context.Context, instance runtimeobject.RuntimeObject) (reconcile.Result, errors.OperatorError) { lc := instance.(*kcpv1alpha1.LogicalCluster) workspaceName := getWorkspaceName(lc) @@ -52,20 +53,16 @@ func (r *workspaceAuthSubroutine) Process(ctx context.Context, instance lifecycl return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to get workspace path"), true, false) } - //TODO use ctx after migrating to multi-cluster runtime - ctxWithTimeout, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - var domainCASecret corev1.Secret if r.cfg.DomainCALookup { - err := r.runtimeClient.Get(ctxWithTimeout, client.ObjectKey{Name: "domain-certificate-ca", Namespace: "platform-mesh-system"}, &domainCASecret) + err := r.runtimeClient.Get(ctx, client.ObjectKey{Name: "domain-certificate-ca", Namespace: "platform-mesh-system"}, &domainCASecret) if err != nil { return reconcile.Result{}, errors.NewOperatorError(fmt.Errorf("failed to get domain CA secret: %w", err), true, false) } } obj := &kcptenancyv1alphav1.WorkspaceAuthenticationConfiguration{ObjectMeta: metav1.ObjectMeta{Name: workspaceName}} - _, err := controllerutil.CreateOrUpdate(ctxWithTimeout, r.client, obj, func() error { + _, err := controllerutil.CreateOrUpdate(ctx, r.client, obj, func() error { obj.Spec = kcptenancyv1alphav1.WorkspaceAuthenticationConfigurationSpec{ JWT: []kcptenancyv1alphav1.JWTAuthenticator{ { diff --git a/internal/subroutine/workspace_initializer.go b/internal/subroutine/workspace_initializer.go index 21aad8a..4f18f3b 100644 --- a/internal/subroutine/workspace_initializer.go +++ b/internal/subroutine/workspace_initializer.go @@ -4,29 +4,25 @@ import ( "context" "fmt" "os" - "slices" "strings" - "time" kcpv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1" accountsv1alpha1 "github.com/platform-mesh/account-operator/api/v1alpha1" - lifecycleruntimeobject "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" + "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" lifecyclesubroutine "github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine" "github.com/platform-mesh/golang-commons/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" "github.com/platform-mesh/security-operator/api/v1alpha1" "github.com/platform-mesh/security-operator/internal/config" ) -const initializerName = "root:security" - -func NewWorkspaceInitializer(cl, orgsClient client.Client, restCfg *rest.Config, cfg config.Config) *workspaceInitializer { +func NewWorkspaceInitializer(orgsClient client.Client, cfg config.Config, mgr mcmanager.Manager) *workspaceInitializer { coreModulePath := cfg.CoreModulePath // read file from path @@ -36,47 +32,41 @@ func NewWorkspaceInitializer(cl, orgsClient client.Client, restCfg *rest.Config, } return &workspaceInitializer{ - cl: cl, - orgsClient: orgsClient, - restCfg: restCfg, - coreModule: string(res), + orgsClient: orgsClient, + coreModule: string(res), + initializerName: cfg.InitializerName, + mgr: mgr, } } var _ lifecyclesubroutine.Subroutine = &workspaceInitializer{} type workspaceInitializer struct { - cl client.Client - orgsClient client.Client - restCfg *rest.Config - coreModule string + orgsClient client.Client + mgr mcmanager.Manager + coreModule string + initializerName string } -func (w *workspaceInitializer) Finalize(ctx context.Context, instance lifecycleruntimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { +func (w *workspaceInitializer) Finalize(ctx context.Context, instance runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { // TODO: implement once finalizing workspaces are a thing return ctrl.Result{}, nil } -func (w *workspaceInitializer) Finalizers() []string { return nil } +func (w *workspaceInitializer) Finalizers(_ runtimeobject.RuntimeObject) []string { + return nil +} func (w *workspaceInitializer) GetName() string { return "WorkspaceInitializer" } -func (w *workspaceInitializer) Process(ctx context.Context, instance lifecycleruntimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { +func (w *workspaceInitializer) Process(ctx context.Context, instance runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { lc := instance.(*kcpv1alpha1.LogicalCluster) - path, ok := lc.Annotations["kcp.io/path"] - if !ok { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("unable to get workspace path"), true, false) - } - store := v1alpha1.Store{ ObjectMeta: metav1.ObjectMeta{Name: generateStoreName(lc)}, } - //TODO use ctx after migrating to multi-cluster runtime - ctxWithTimeout,cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - _, err := controllerutil.CreateOrUpdate(ctxWithTimeout, w.orgsClient, &store, func() error { + _, err := controllerutil.CreateOrUpdate(ctx, w.orgsClient, &store, func() error { store.Spec = v1alpha1.StoreSpec{ Tuples: []v1alpha1.Tuple{ { @@ -104,18 +94,15 @@ func (w *workspaceInitializer) Process(ctx context.Context, instance lifecycleru return ctrl.Result{Requeue: true}, nil } - // Update accountInfo with storeid - wsCfg := rest.CopyConfig(w.restCfg) - wsCfg.Host = strings.ReplaceAll(wsCfg.Host, "/services/initializingworkspaces/root:security", "/clusters/"+path) - wsClient, err := client.New(wsCfg, client.Options{Scheme: w.cl.Scheme()}) + cluster, err := w.mgr.ClusterFromContext(ctx) if err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("unable to create client: %w", err), true, true) + 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"}, } - _, err = controllerutil.CreateOrUpdate(ctxWithTimeout, wsClient, &accountInfo, func() error { + _, err = controllerutil.CreateOrUpdate(ctx, cluster.GetClient(), &accountInfo, func() error { accountInfo.Spec.FGA.Store.Id = store.Status.StoreID return nil }) @@ -123,16 +110,6 @@ func (w *workspaceInitializer) Process(ctx context.Context, instance lifecycleru return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("unable to create/update accountInfo: %w", err), true, true) } - original := lc.DeepCopy() - lc.Status.Initializers = slices.DeleteFunc(lc.Status.Initializers, func(s kcpv1alpha1.LogicalClusterInitializer) bool { - return s == initializerName - }) - - err = w.cl.Status().Patch(ctx, lc, client.MergeFrom(original)) - if err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("unable to patch out initializers: %w", err), true, true) - } - return ctrl.Result{}, nil }