diff --git a/cmd/operator.go b/cmd/operator.go index 8cc215b..f463db6 100644 --- a/cmd/operator.go +++ b/cmd/operator.go @@ -128,14 +128,14 @@ func RunController(_ *cobra.Command, _ []string) { // coverage-ignore log.Fatal().Err(err).Msg("unable to create orgs client") } - accountReconciler := controller.NewAccountReconciler(log, mgr, operatorCfg, orgsClient) - if err := accountReconciler.SetupWithManager(mgr, defaultCfg, log); err != nil { + accountReconciler := controller.NewAccountReconciler(mgr, operatorCfg, orgsClient) + if err := accountReconciler.SetupWithManager(mgr); err != nil { log.Fatal().Err(err).Str("controller", "Account").Msg("unable to create controller") } if operatorCfg.Controllers.AccountInfo.Enabled { - accountInfoReconciler := controller.NewAccountInfoReconciler(log, mgr, operatorCfg) - if err := accountInfoReconciler.SetupWithManager(mgr, defaultCfg, log); err != nil { + accountInfoReconciler := controller.NewAccountInfoReconciler(mgr, operatorCfg) + if err := accountInfoReconciler.SetupWithManager(mgr); err != nil { log.Fatal().Err(err).Str("controller", "AccountInfo").Msg("unable to create controller") } } diff --git a/go.mod b/go.mod index c5e7975..4b41073 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,7 @@ module github.com/platform-mesh/account-operator go 1.25.7 -replace ( - k8s.io/api => k8s.io/api v0.35.2 - k8s.io/apimachinery => k8s.io/apimachinery v0.35.2 - k8s.io/client-go => k8s.io/client-go v0.35.2 -) +replace github.com/platform-mesh/subroutines => ../subroutines require ( github.com/go-logr/logr v1.4.3 @@ -14,6 +10,7 @@ require ( github.com/kcp-dev/multicluster-provider v0.5.1 github.com/kcp-dev/sdk v0.30.0 github.com/platform-mesh/golang-commons v0.13.2 + github.com/platform-mesh/subroutines v0.0.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 @@ -29,7 +26,6 @@ require ( ) require ( - github.com/99designs/gqlgen v0.17.87 // 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 @@ -64,16 +60,13 @@ require ( 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.38.2 // 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.2 // indirect github.com/prometheus/client_model v0.6.2 // 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/sosodev/duration v1.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/vektah/gqlparser/v2 v2.5.32 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel v1.41.0 // indirect @@ -93,6 +86,7 @@ require ( golang.org/x/term v0.40.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.42.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect diff --git a/go.sum b/go.sum index af364df..ef4f9aa 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,7 @@ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= -github.com/99designs/gqlgen v0.17.87 h1:pSnCIMhBQezAE8bc1GNmfdLXFmnWtWl1GRDFEE/nHP8= -github.com/99designs/gqlgen v0.17.87/go.mod h1:fK05f1RqSNfQpd4CfW5qk/810Tqi4/56Wf6Nem0khAg= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= -github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= -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= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -151,10 +145,6 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= -github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= -github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -167,8 +157,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/vektah/gqlparser/v2 v2.5.32 h1:k9QPJd4sEDTL+qB4ncPLflqTJ3MmjB9SrVzJrawpFSc= -github.com/vektah/gqlparser/v2 v2.5.32/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= diff --git a/internal/controller/account_controller.go b/internal/controller/account_controller.go index 9332529..234e5a7 100644 --- a/internal/controller/account_controller.go +++ b/internal/controller/account_controller.go @@ -3,20 +3,18 @@ package controller import ( "context" - platformmeshconfig "github.com/platform-mesh/golang-commons/config" - "github.com/platform-mesh/golang-commons/controller/lifecycle/builder" - mclifecycle "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" + "github.com/platform-mesh/subroutines" + "github.com/platform-mesh/subroutines/conditions" + "github.com/platform-mesh/subroutines/lifecycle" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/predicate" - mccontext "sigs.k8s.io/multicluster-runtime/pkg/context" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + mcbuilder "sigs.k8s.io/multicluster-runtime/pkg/builder" mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" mcreconcile "sigs.k8s.io/multicluster-runtime/pkg/reconcile" + "k8s.io/client-go/rest" + "github.com/platform-mesh/account-operator/api/v1alpha1" "github.com/platform-mesh/account-operator/internal/config" "github.com/platform-mesh/account-operator/pkg/subroutines/manageaccountinfo" @@ -25,23 +23,20 @@ import ( "github.com/platform-mesh/account-operator/pkg/subroutines/workspacetype" ) -const ( - operatorName = "account-operator" - accountReconcilerName = "AccountReconciler" -) +const accountReconcilerName = "AccountReconciler" // AccountReconciler orchestrates Account resources across logical clusters. type AccountReconciler struct { cfg config.OperatorConfig - lifecycle *mclifecycle.LifecycleManager + lifecycle *lifecycle.Lifecycle } -func NewAccountReconciler(log *logger.Logger, mgr mcmanager.Manager, cfg config.OperatorConfig, orgsClient client.Client) *AccountReconciler { // coverage-ignore +func NewAccountReconciler(mgr mcmanager.Manager, cfg config.OperatorConfig, orgsClient client.Client) *AccountReconciler { // coverage-ignore localMgr := mgr.GetLocalManager() localCfg := rest.CopyConfig(localMgr.GetConfig()) serverCA := string(localCfg.CAData) - subs := []lifecyclesubroutine.Subroutine{} + subs := []subroutines.Subroutine{} if cfg.Subroutines.WorkspaceType.Enabled { subs = append(subs, workspacetype.New(orgsClient)) @@ -59,18 +54,27 @@ func NewAccountReconciler(log *logger.Logger, mgr mcmanager.Manager, cfg config. subs = append(subs, workspaceready.New(mgr)) } + lc := lifecycle.New(mgr, accountReconcilerName, func() client.Object { return &v1alpha1.Account{} }, subs...). + WithConditions(conditions.NewManager()) + return &AccountReconciler{ - cfg: cfg, - lifecycle: builder.NewBuilder(operatorName, accountReconcilerName, subs, log). - WithConditionManagement(). - BuildMultiCluster(mgr), + cfg: cfg, + lifecycle: lc, } } -func (r *AccountReconciler) SetupWithManager(mgr mcmanager.Manager, cfg *platformmeshconfig.CommonServiceConfig, log *logger.Logger, eventPredicates ...predicate.Predicate) error { // coverage-ignore - return r.lifecycle.SetupWithManager(mgr, cfg.MaxConcurrentReconciles, accountReconcilerName, &v1alpha1.Account{}, cfg.DebugLabelValue, r, log, eventPredicates...) +func (r *AccountReconciler) SetupWithManager(mgr mcmanager.Manager, eventPredicates ...predicate.Predicate) error { // coverage-ignore + builder := mcbuilder.ControllerManagedBy(mgr). + Named(accountReconcilerName). + For(&v1alpha1.Account{}) + + for _, p := range eventPredicates { + builder = builder.WithEventFilter(p) + } + + return builder.Complete(r) } -func (r *AccountReconciler) Reconcile(ctx context.Context, req mcreconcile.Request) (ctrl.Result, error) { // coverage-ignore - return r.lifecycle.Reconcile(mccontext.WithCluster(ctx, req.ClusterName), req, &v1alpha1.Account{}) +func (r *AccountReconciler) Reconcile(ctx context.Context, req mcreconcile.Request) (reconcile.Result, error) { // coverage-ignore + return r.lifecycle.Reconcile(ctx, req) } diff --git a/internal/controller/account_controller_test.go b/internal/controller/account_controller_test.go index e61ca10..c9c24bb 100644 --- a/internal/controller/account_controller_test.go +++ b/internal/controller/account_controller_test.go @@ -15,7 +15,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" kcptenancyv1alpha "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" - platformmeshconfig "github.com/platform-mesh/golang-commons/config" platformmeshcontext "github.com/platform-mesh/golang-commons/context" "github.com/platform-mesh/golang-commons/logger" "github.com/stretchr/testify/suite" @@ -98,9 +97,8 @@ func (s *AccountTestSuite) SetupSuite() { cfg.Subroutines.AccountInfo.Enabled = true cfg.Subroutines.WorkspaceType.Enabled = true cfg.Kcp.ProviderWorkspace = core.RootCluster.Path().String() - dCfg := &platformmeshconfig.CommonServiceConfig{} - accountReconciler := controller.NewAccountReconciler(logger, s.mgr, cfg, s.rootOrgsClient) - s.Require().NoError(accountReconciler.SetupWithManager(s.mgr, dCfg, logger)) + accountReconciler := controller.NewAccountReconciler(s.mgr, cfg, s.rootOrgsClient) + s.Require().NoError(accountReconciler.SetupWithManager(s.mgr)) s.startManager() s.setupDefaultOrg() @@ -158,11 +156,11 @@ func (s *AccountTestSuite) TestWorkspaceCreation() { if err := s.rootOrgsDefaultClient.Get(testContext, types.NamespacedName{Name: accountName}, updatedAccount); err != nil { return false } - return meta.IsStatusConditionTrue(updatedAccount.Status.Conditions, "WorkspaceSubroutine_Ready") + return meta.IsStatusConditionTrue(updatedAccount.Status.Conditions, "WorkspaceSubroutine") }, defaultTestTimeout, defaultTickInterval) s.verifyWorkspace(testContext, "default", accountName) - s.verifyCondition(updatedAccount.Status.Conditions, "WorkspaceSubroutine_Ready", metav1.ConditionTrue, "Complete") + s.verifyCondition(updatedAccount.Status.Conditions, "WorkspaceSubroutine", metav1.ConditionTrue, "Complete") } func (s *AccountTestSuite) TestAccountInfoCreationForOrganization() { @@ -177,7 +175,7 @@ func (s *AccountTestSuite) TestAccountInfoCreationForOrganization() { if err := s.rootOrgsClient.Get(testContext, types.NamespacedName{Name: accountName}, createdAccount); err != nil { return false } - return meta.IsStatusConditionTrue(createdAccount.Status.Conditions, "ManageAccountInfoSubroutine_Ready") + return meta.IsStatusConditionTrue(createdAccount.Status.Conditions, "ManageAccountInfoSubroutine") }, defaultTestTimeout, defaultTickInterval) accountInfo := &v1alpha1.AccountInfo{} diff --git a/internal/controller/accountinfo_controller.go b/internal/controller/accountinfo_controller.go index 05bdd15..a79bb0e 100644 --- a/internal/controller/accountinfo_controller.go +++ b/internal/controller/accountinfo_controller.go @@ -3,14 +3,12 @@ package controller import ( "context" - platformmeshconfig "github.com/platform-mesh/golang-commons/config" - "github.com/platform-mesh/golang-commons/controller/lifecycle/builder" - mclifecycle "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" - ctrl "sigs.k8s.io/controller-runtime" + "github.com/platform-mesh/subroutines" + "github.com/platform-mesh/subroutines/lifecycle" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/predicate" - mccontext "sigs.k8s.io/multicluster-runtime/pkg/context" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + mcbuilder "sigs.k8s.io/multicluster-runtime/pkg/builder" mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" mcreconcile "sigs.k8s.io/multicluster-runtime/pkg/reconcile" @@ -24,27 +22,36 @@ const accountInfoReconcilerName = "AccountInfoReconciler" // AccountInfoReconciler orchestrates AccountInfo resources across logical clusters. type AccountInfoReconciler struct { cfg config.OperatorConfig - lifecycle *mclifecycle.LifecycleManager + lifecycle *lifecycle.Lifecycle } -func NewAccountInfoReconciler(log *logger.Logger, mgr mcmanager.Manager, cfg config.OperatorConfig) *AccountInfoReconciler { // coverage-ignore - subs := []lifecyclesubroutine.Subroutine{} +func NewAccountInfoReconciler(mgr mcmanager.Manager, cfg config.OperatorConfig) *AccountInfoReconciler { // coverage-ignore + subs := []subroutines.Subroutine{} if cfg.Controllers.AccountInfo.Enabled { subs = append(subs, finalizeaccountinfo.New(mgr)) } + lc := lifecycle.New(mgr, accountInfoReconcilerName, func() client.Object { return &v1alpha1.AccountInfo{} }, subs...) + return &AccountInfoReconciler{ - cfg: cfg, - lifecycle: builder.NewBuilder(operatorName, accountInfoReconcilerName, subs, log). - BuildMultiCluster(mgr), + cfg: cfg, + lifecycle: lc, } } -func (r *AccountInfoReconciler) SetupWithManager(mgr mcmanager.Manager, cfg *platformmeshconfig.CommonServiceConfig, log *logger.Logger, eventPredicates ...predicate.Predicate) error { // coverage-ignore - return r.lifecycle.SetupWithManager(mgr, cfg.MaxConcurrentReconciles, accountInfoReconcilerName, &v1alpha1.AccountInfo{}, cfg.DebugLabelValue, r, log, eventPredicates...) +func (r *AccountInfoReconciler) SetupWithManager(mgr mcmanager.Manager, eventPredicates ...predicate.Predicate) error { // coverage-ignore + builder := mcbuilder.ControllerManagedBy(mgr). + Named(accountInfoReconcilerName). + For(&v1alpha1.AccountInfo{}) + + for _, p := range eventPredicates { + builder = builder.WithEventFilter(p) + } + + return builder.Complete(r) } -func (r *AccountInfoReconciler) Reconcile(ctx context.Context, req mcreconcile.Request) (ctrl.Result, error) { // coverage-ignore - return r.lifecycle.Reconcile(mccontext.WithCluster(ctx, req.ClusterName), req, &v1alpha1.AccountInfo{}) +func (r *AccountInfoReconciler) Reconcile(ctx context.Context, req mcreconcile.Request) (reconcile.Result, error) { // coverage-ignore + return r.lifecycle.Reconcile(ctx, req) } diff --git a/pkg/clusteredname/clusteredname.go b/pkg/clusteredname/clusteredname.go index b042804..1198b4e 100644 --- a/pkg/clusteredname/clusteredname.go +++ b/pkg/clusteredname/clusteredname.go @@ -4,7 +4,8 @@ import ( "context" "github.com/kcp-dev/logicalcluster/v3" - "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" + "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/apimachinery/pkg/types" mccontext "sigs.k8s.io/multicluster-runtime/pkg/context" ) @@ -14,7 +15,7 @@ type ClusteredName struct { ClusterID logicalcluster.Name } -func GetClusteredName(ctx context.Context, instance runtimeobject.RuntimeObject) (ClusteredName, bool) { +func GetClusteredName(ctx context.Context, instance client.Object) (ClusteredName, bool) { clusterName, ok := mccontext.ClusterFrom(ctx) cn := ClusteredName{ NamespacedName: types.NamespacedName{ @@ -28,7 +29,7 @@ func GetClusteredName(ctx context.Context, instance runtimeobject.RuntimeObject) return cn, ok } -func MustGetClusteredName(ctx context.Context, instance runtimeobject.RuntimeObject) ClusteredName { +func MustGetClusteredName(ctx context.Context, instance client.Object) ClusteredName { if cn, ok := GetClusteredName(ctx, instance); ok { return cn } diff --git a/pkg/subroutines/finalizeaccountinfo/finalize_accountinfo.go b/pkg/subroutines/finalizeaccountinfo/finalize_accountinfo.go index 3bf6e7e..0e29d5c 100644 --- a/pkg/subroutines/finalizeaccountinfo/finalize_accountinfo.go +++ b/pkg/subroutines/finalizeaccountinfo/finalize_accountinfo.go @@ -3,23 +3,20 @@ package finalizeaccountinfo import ( "context" "fmt" + "time" - "github.com/platform-mesh/golang-commons/controller/lifecycle/ratelimiter" - "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/platform-mesh/golang-commons/logger" + "github.com/platform-mesh/subroutines" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/client-go/util/workqueue" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" "github.com/platform-mesh/account-operator/api/v1alpha1" ) -var _ subroutine.Subroutine = (*FinalizeAccountInfoSubroutine)(nil) +var _ subroutines.Finalizer = (*FinalizeAccountInfoSubroutine)(nil) const ( FinalizeAccountInfoSubroutineName = "FinalizeAccountInfoSubroutine" @@ -32,46 +29,44 @@ type FinalizeAccountInfoSubroutine struct { } func New(mgr mcmanager.Manager) *FinalizeAccountInfoSubroutine { - rl, _ := ratelimiter.NewStaticThenExponentialRateLimiter[*v1alpha1.AccountInfo](ratelimiter.NewConfig()) //nolint:errcheck - return &FinalizeAccountInfoSubroutine{mgr: mgr, limiter: rl} + return &FinalizeAccountInfoSubroutine{ + mgr: mgr, + limiter: workqueue.NewTypedItemExponentialFailureRateLimiter[*v1alpha1.AccountInfo](1*time.Second, 120*time.Second), + } } func (r *FinalizeAccountInfoSubroutine) GetName() string { return FinalizeAccountInfoSubroutineName } -func (r *FinalizeAccountInfoSubroutine) Finalizers(_ runtimeobject.RuntimeObject) []string { // coverage-ignore +func (r *FinalizeAccountInfoSubroutine) Finalizers(_ client.Object) []string { // coverage-ignore return []string{AccountInfoFinalizer} } -func (r *FinalizeAccountInfoSubroutine) Process(_ context.Context, _ runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { - return ctrl.Result{}, nil -} - -func (r *FinalizeAccountInfoSubroutine) Finalize(ctx context.Context, ro runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { - instance := ro.(*v1alpha1.AccountInfo) - log := logger.LoadLoggerFromContext(ctx) +func (r *FinalizeAccountInfoSubroutine) Finalize(ctx context.Context, obj client.Object) (subroutines.Result, error) { + instance := obj.(*v1alpha1.AccountInfo) + logger := log.FromContext(ctx) cluster, err := r.mgr.ClusterFromContext(ctx) if err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("getting cluster from context: %w", err), true, true) + return subroutines.OK(), fmt.Errorf("getting cluster from context: %w", err) } clusterClient := cluster.GetClient() list := &v1alpha1.AccountList{} if err := clusterClient.List(ctx, list, &client.ListOptions{}); err != nil { if !kerrors.IsNotFound(err) && !meta.IsNoMatchError(err) { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("listing child accounts: %w", err), true, true) + return subroutines.OK(), fmt.Errorf("listing child accounts: %w", err) } } if len(list.Items) > 0 { - log.Info().Msgf("Found %d accounts, cannot finalize AccountInfo yet", len(list.Items)) - return ctrl.Result{RequeueAfter: r.limiter.When(instance)}, nil + logger.Info("cannot finalize AccountInfo yet", "accountCount", len(list.Items)) + return subroutines.OKWithRequeue(r.limiter.When(instance)), nil } - log.Info().Msg("No accounts found in cluster, AccountInfo can be finalized") + logger.Info("no accounts found in cluster, AccountInfo can be finalized") r.limiter.Forget(instance) - return ctrl.Result{}, nil + return subroutines.OK(), nil } diff --git a/pkg/subroutines/finalizeaccountinfo/finalize_accountinfo_test.go b/pkg/subroutines/finalizeaccountinfo/finalize_accountinfo_test.go index f28fa1f..63232cf 100644 --- a/pkg/subroutines/finalizeaccountinfo/finalize_accountinfo_test.go +++ b/pkg/subroutines/finalizeaccountinfo/finalize_accountinfo_test.go @@ -5,12 +5,14 @@ import ( "fmt" "testing" + "github.com/go-logr/logr/testr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/cluster" + crlog "sigs.k8s.io/controller-runtime/pkg/log" mccontext "sigs.k8s.io/multicluster-runtime/pkg/context" mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" "sigs.k8s.io/multicluster-runtime/pkg/multicluster" @@ -18,9 +20,6 @@ import ( "github.com/platform-mesh/account-operator/api/v1alpha1" "github.com/platform-mesh/account-operator/pkg/subroutines/finalizeaccountinfo" "github.com/platform-mesh/account-operator/pkg/subroutines/mocks" - "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" - "github.com/platform-mesh/golang-commons/logger" - "github.com/platform-mesh/golang-commons/logger/testlogger" ) var _ multicluster.Provider = &Provider{} @@ -51,17 +50,10 @@ func TestFinalizeAccountInfoFinalizers(t *testing.T) { assert.Equal(t, []string{finalizeaccountinfo.AccountInfoFinalizer}, s.Finalizers(nil)) } -func TestFinalizeAccountInfoProcess(t *testing.T) { - s := finalizeaccountinfo.New(nil) - result, err := s.Process(t.Context(), nil) - assert.Nil(t, err) - assert.Zero(t, result.RequeueAfter) -} - func TestFinalizeAccountInfoFinalize(t *testing.T) { testCases := []struct { name string - obj runtimeobject.RuntimeObject + obj client.Object clusters map[string]cluster.Cluster expectError bool expectRequeue bool @@ -145,18 +137,17 @@ func TestFinalizeAccountInfoFinalize(t *testing.T) { s := finalizeaccountinfo.New(mgr) ctx := t.Context() - log := testlogger.New() - ctx = logger.SetLoggerInContext(ctx, log.Logger) + ctx = crlog.IntoContext(ctx, testr.New(t)) ctx = mccontext.WithCluster(ctx, "test-cluster") result, processErr := s.Finalize(ctx, tc.obj) if tc.expectError { - assert.Error(t, processErr.Err()) + assert.Error(t, processErr) } else { - assert.Nil(t, processErr) + assert.NoError(t, processErr) } if tc.expectRequeue { - assert.True(t, result.RequeueAfter > 0) + assert.True(t, result.Requeue() > 0) } }) } diff --git a/pkg/subroutines/manageaccountinfo/manage_accountinfo.go b/pkg/subroutines/manageaccountinfo/manage_accountinfo.go index cbc9c1f..92c8d9d 100644 --- a/pkg/subroutines/manageaccountinfo/manage_accountinfo.go +++ b/pkg/subroutines/manageaccountinfo/manage_accountinfo.go @@ -3,29 +3,28 @@ package manageaccountinfo import ( "context" "fmt" - "strings" + "net/url" + "path" "time" kcpcorev1alpha "github.com/kcp-dev/sdk/apis/core/v1alpha1" kcptenancyv1alpha "github.com/kcp-dev/sdk/apis/tenancy/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/platform-mesh/subroutines" kerrors "k8s.io/apimachinery/pkg/api/errors" - - "github.com/platform-mesh/golang-commons/logger" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/util/workqueue" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" "github.com/platform-mesh/account-operator/api/v1alpha1" "github.com/platform-mesh/account-operator/pkg/clusteredname" + + ctrl "sigs.k8s.io/controller-runtime" ) -var _ subroutine.Subroutine = (*ManageAccountInfoSubroutine)(nil) +var _ subroutines.Processor = (*ManageAccountInfoSubroutine)(nil) const ( ManageAccountInfoSubroutineName = "ManageAccountInfoSubroutine" @@ -47,40 +46,32 @@ func (r *ManageAccountInfoSubroutine) GetName() string { return ManageAccountInfoSubroutineName } -func (r *ManageAccountInfoSubroutine) Finalizers(_ runtimeobject.RuntimeObject) []string { // coverage-ignore - return []string{} -} - -func (r *ManageAccountInfoSubroutine) Finalize(_ context.Context, _ runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { - return ctrl.Result{}, nil -} - -func (r *ManageAccountInfoSubroutine) Process(ctx context.Context, ro runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { - instance := ro.(*v1alpha1.Account) +func (r *ManageAccountInfoSubroutine) Process(ctx context.Context, obj client.Object) (subroutines.Result, error) { + instance := obj.(*v1alpha1.Account) - log := logger.LoadLoggerFromContext(ctx) - cn := clusteredname.MustGetClusteredName(ctx, ro) + logger := log.FromContext(ctx) + cn := clusteredname.MustGetClusteredName(ctx, obj) clusterRef, err := r.mgr.GetCluster(ctx, string(cn.ClusterID)) if err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("getting cluster: %w", err), true, true) + return subroutines.OK(), fmt.Errorf("getting cluster: %w", err) } clusterClient := clusterRef.GetClient() accountWorkspace := &kcptenancyv1alpha.Workspace{} if err := clusterClient.Get(ctx, client.ObjectKey{Name: instance.Name}, accountWorkspace); err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("getting Account's Workspace: %w", err), true, true) + return subroutines.OK(), fmt.Errorf("getting Account's Workspace: %w", err) } if accountWorkspace.Status.Phase != kcpcorev1alpha.LogicalClusterPhaseInitializing && accountWorkspace.Status.Phase != kcpcorev1alpha.LogicalClusterPhaseReady { - log.Info().Msg("workspace is not ready yet, retry") - return ctrl.Result{RequeueAfter: r.limiter.When(instance)}, nil + logger.Info("workspace is not ready yet, retry") + return subroutines.OKWithRequeue(r.limiter.When(instance)), nil } // Retrieve logical cluster currentWorkspacePath, currentWorkspaceUrl, err := r.retrieveCurrentWorkspacePath(accountWorkspace) if err != nil { - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + return subroutines.OK(), err } selfAccountLocation := v1alpha1.AccountLocation{ @@ -94,7 +85,7 @@ func (r *ManageAccountInfoSubroutine) Process(ctx context.Context, ro runtimeobj accountCluster, err := r.mgr.GetCluster(ctx, accountWorkspace.Spec.Cluster) if err != nil { - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + return subroutines.OK(), err } accountClusterClient := accountCluster.GetClient() @@ -109,20 +100,21 @@ func (r *ManageAccountInfoSubroutine) Process(ctx context.Context, ro runtimeobj accountInfo.Spec.ClusterInfo.CA = r.serverCA return nil }); err != nil { - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + return subroutines.OK(), err } r.limiter.Forget(instance) - return ctrl.Result{}, nil + return subroutines.OK(), nil } // Create AccountInfo for a non-organization Account based on its parent's // AccountInfo var parentAccountInfo v1alpha1.AccountInfo if err := clusterClient.Get(ctx, client.ObjectKey{Name: DefaultAccountInfoName}, &parentAccountInfo); kerrors.IsNotFound(err) { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("AccountInfo does not yet exist"), true, false) + logger.Info("parent AccountInfo does not yet exist, retry") + return subroutines.OKWithRequeue(r.limiter.When(instance)), nil } else if err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("getting parent AccountInfo: %w", err), true, true) + return subroutines.OK(), fmt.Errorf("getting parent AccountInfo: %w", err) } accountInfo := &v1alpha1.AccountInfo{ObjectMeta: v1.ObjectMeta{Name: DefaultAccountInfoName}} @@ -135,11 +127,11 @@ func (r *ManageAccountInfoSubroutine) Process(ctx context.Context, ro runtimeobj accountInfo.Spec.ClusterInfo.CA = r.serverCA return nil }); err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("creating or updating AccountInfo %w", err), true, true) + return subroutines.OK(), fmt.Errorf("creating or updating AccountInfo %w", err) } r.limiter.Forget(instance) - return ctrl.Result{}, nil + return subroutines.OK(), nil } func (r *ManageAccountInfoSubroutine) retrieveCurrentWorkspacePath(ws *kcptenancyv1alpha.Workspace) (string, string, error) { @@ -147,15 +139,14 @@ func (r *ManageAccountInfoSubroutine) retrieveCurrentWorkspacePath(ws *kcptenanc return "", "", fmt.Errorf("workspace URL is empty") } - // Parse path from URL - split := strings.Split(ws.Spec.URL, "/") - if len(split) < 3 { - return "", "", fmt.Errorf("workspace URL is invalid") + parsed, err := url.Parse(ws.Spec.URL) + if err != nil { + return "", "", fmt.Errorf("parsing workspace URL: %w", err) } - lastSegment := split[len(split)-1] - if lastSegment == "" || strings.TrimSpace(lastSegment) == "" { - return "", "", fmt.Errorf("workspace URL is empty") + lastSegment := path.Base(parsed.Path) + if lastSegment == "" || lastSegment == "." || lastSegment == "/" { + return "", "", fmt.Errorf("workspace URL has no path segment") } return lastSegment, ws.Spec.URL, nil } diff --git a/pkg/subroutines/manageaccountinfo/manage_accountinfo_test.go b/pkg/subroutines/manageaccountinfo/manage_accountinfo_test.go index c555add..93088a5 100644 --- a/pkg/subroutines/manageaccountinfo/manage_accountinfo_test.go +++ b/pkg/subroutines/manageaccountinfo/manage_accountinfo_test.go @@ -4,7 +4,9 @@ import ( "context" "fmt" "testing" + "time" + "github.com/go-logr/logr/testr" kcpcorev1alpha "github.com/kcp-dev/sdk/apis/core/v1alpha1" kcptenancyv1alpha "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" "github.com/stretchr/testify/assert" @@ -15,6 +17,7 @@ import ( "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/cluster" + crlog "sigs.k8s.io/controller-runtime/pkg/log" mccontext "sigs.k8s.io/multicluster-runtime/pkg/context" mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" "sigs.k8s.io/multicluster-runtime/pkg/multicluster" @@ -22,9 +25,6 @@ import ( "github.com/platform-mesh/account-operator/api/v1alpha1" "github.com/platform-mesh/account-operator/pkg/subroutines/manageaccountinfo" "github.com/platform-mesh/account-operator/pkg/subroutines/mocks" - "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" - "github.com/platform-mesh/golang-commons/logger" - "github.com/platform-mesh/golang-commons/logger/testlogger" ) var _ multicluster.Provider = &Provider{} @@ -52,17 +52,6 @@ func TestManageAccountInfoGetName(t *testing.T) { assert.Equal(t, manageaccountinfo.ManageAccountInfoSubroutineName, (&manageaccountinfo.ManageAccountInfoSubroutine{}).GetName()) } -func TestManageAccountInfoFinalizers(t *testing.T) { - assert.Equal(t, []string{}, (&manageaccountinfo.ManageAccountInfoSubroutine{}).Finalizers(nil)) -} - -func TestManageAccountInfoFinalize(t *testing.T) { - s := manageaccountinfo.New(nil, "") - result, err := s.Finalize(t.Context(), nil) - assert.Nil(t, err) - assert.Zero(t, result.RequeueAfter) -} - func TestManageAccountInfoProcess(t *testing.T) { accountObj := func(tp v1alpha1.AccountType) *v1alpha1.Account { return &v1alpha1.Account{ @@ -73,7 +62,7 @@ func TestManageAccountInfoProcess(t *testing.T) { testCases := []struct { name string - obj runtimeobject.RuntimeObject + obj client.Object clusters map[string]cluster.Cluster expectError bool expectRequeue bool @@ -322,8 +311,8 @@ func TestManageAccountInfoProcess(t *testing.T) { return c }(), }, - obj: accountObj(v1alpha1.AccountTypeAccount), - expectError: true, + obj: accountObj(v1alpha1.AccountTypeAccount), + expectRequeue: true, }, { name: "org account success", @@ -377,18 +366,20 @@ func TestManageAccountInfoProcess(t *testing.T) { s := manageaccountinfo.New(mgr, "") ctx := t.Context() - log := testlogger.New() - ctx = logger.SetLoggerInContext(ctx, log.Logger) + ctx = crlog.IntoContext(ctx, testr.New(t)) if test.clusters != nil { ctx = mccontext.WithCluster(ctx, "test-cluster") } - _, processErr := s.Process(ctx, test.obj) + result, processErr := s.Process(ctx, test.obj) if test.expectError { - assert.Error(t, processErr.Err()) + assert.Error(t, processErr) } else { - assert.Nil(t, processErr) + assert.NoError(t, processErr) + } + if test.expectRequeue { + assert.Greater(t, result.Requeue(), time.Duration(0)) } }) } diff --git a/pkg/subroutines/workspace/workspace.go b/pkg/subroutines/workspace/workspace.go index 34ef0a6..8bf6fa7 100644 --- a/pkg/subroutines/workspace/workspace.go +++ b/pkg/subroutines/workspace/workspace.go @@ -2,18 +2,16 @@ package workspace import ( "context" + "time" kcptenancyv1alpha "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" conditionsapi "github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" conditionshelper "github.com/kcp-dev/sdk/apis/third_party/conditions/util/conditions" - "github.com/platform-mesh/golang-commons/controller/lifecycle/ratelimiter" - "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" - "github.com/platform-mesh/golang-commons/errors" + "github.com/platform-mesh/subroutines" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/util/workqueue" - 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" @@ -30,6 +28,11 @@ const ( orgsWorkspacePath = "root:orgs" ) +var ( + _ subroutines.Processor = &WorkspaceSubroutine{} + _ subroutines.Finalizer = &WorkspaceSubroutine{} +) + type WorkspaceSubroutine struct { mgr mcmanager.Manager limiter workqueue.TypedRateLimiter[*v1alpha1.Account] @@ -37,10 +40,9 @@ type WorkspaceSubroutine struct { } func New(mgr mcmanager.Manager, orgsClient client.Client) *WorkspaceSubroutine { - rl, _ := ratelimiter.NewStaticThenExponentialRateLimiter[*v1alpha1.Account](ratelimiter.NewConfig()) //nolint:errcheck return &WorkspaceSubroutine{ mgr: mgr, - limiter: rl, + limiter: workqueue.NewTypedItemExponentialFailureRateLimiter[*v1alpha1.Account](1*time.Second, 120*time.Second), orgsClient: orgsClient, } } @@ -49,15 +51,15 @@ func (r *WorkspaceSubroutine) GetName() string { return WorkspaceSubroutineName } -func (r *WorkspaceSubroutine) Finalize(ctx context.Context, ro runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { - instance := ro.(*v1alpha1.Account) - cn := clusteredname.MustGetClusteredName(ctx, ro) +func (r *WorkspaceSubroutine) Finalize(ctx context.Context, obj client.Object) (subroutines.Result, error) { + instance := obj.(*v1alpha1.Account) + cn := clusteredname.MustGetClusteredName(ctx, obj) clusterName := cn.ClusterID.String() cluster, err := r.mgr.GetCluster(ctx, clusterName) if err != nil { // coverage-ignore - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + return subroutines.OK(), err } clusterClient := cluster.GetClient() @@ -66,35 +68,35 @@ func (r *WorkspaceSubroutine) Finalize(ctx context.Context, ro runtimeobject.Run if err := clusterClient.Get(ctx, client.ObjectKey{Name: instance.Name}, &ws); err != nil { if kerrors.IsNotFound(err) || meta.IsNoMatchError(err) { r.limiter.Forget(instance) - return ctrl.Result{}, nil + return subroutines.OK(), nil } - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + return subroutines.OK(), err } if ws.GetDeletionTimestamp() != nil { - return ctrl.Result{RequeueAfter: r.limiter.When(instance)}, nil + return subroutines.OKWithRequeue(r.limiter.When(instance)), nil } if err := clusterClient.Delete(ctx, &ws); err != nil { - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + return subroutines.OK(), err } - return ctrl.Result{RequeueAfter: r.limiter.When(instance)}, nil + return subroutines.OKWithRequeue(r.limiter.When(instance)), nil } -func (r *WorkspaceSubroutine) Finalizers(_ runtimeobject.RuntimeObject) []string { // coverage-ignore +func (r *WorkspaceSubroutine) Finalizers(_ client.Object) []string { // coverage-ignore return []string{WorkspaceSubroutineFinalizer} } -func (r *WorkspaceSubroutine) Process(ctx context.Context, ro runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { - instance := ro.(*v1alpha1.Account) - cn := clusteredname.MustGetClusteredName(ctx, ro) +func (r *WorkspaceSubroutine) Process(ctx context.Context, obj client.Object) (subroutines.Result, error) { + instance := obj.(*v1alpha1.Account) + cn := clusteredname.MustGetClusteredName(ctx, obj) clusterName := cn.ClusterID.String() clusterRef, err := r.mgr.GetCluster(ctx, clusterName) if err != nil { - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + return subroutines.OK(), err } clusterClient := clusterRef.GetClient() @@ -103,14 +105,14 @@ func (r *WorkspaceSubroutine) Process(ctx context.Context, ro runtimeobject.Runt accountInfo := &v1alpha1.AccountInfo{} if err := clusterClient.Get(ctx, client.ObjectKey{Name: manageaccountinfo.DefaultAccountInfoName}, accountInfo); err != nil { if kerrors.IsNotFound(err) { - return ctrl.Result{RequeueAfter: r.limiter.When(instance)}, nil + return subroutines.OKWithRequeue(r.limiter.When(instance)), nil } - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + return subroutines.OK(), err } if accountInfo.Spec.Organization.Name == "" { - return ctrl.Result{RequeueAfter: r.limiter.When(instance)}, nil + return subroutines.OKWithRequeue(r.limiter.When(instance)), nil } workspaceTypeName = util.GetWorkspaceTypeName(accountInfo.Spec.Organization.Name, instance.Spec.Type) @@ -118,10 +120,10 @@ func (r *WorkspaceSubroutine) Process(ctx context.Context, ro runtimeobject.Runt ready, err := r.checkWorkspaceTypeReady(ctx, workspaceTypeName) if err != nil { // coverage-ignore - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + return subroutines.OK(), err } if !ready { // coverage-ignore - return ctrl.Result{RequeueAfter: r.limiter.When(instance)}, nil + return subroutines.OKWithRequeue(r.limiter.When(instance)), nil } createdWorkspace := &kcptenancyv1alpha.Workspace{ObjectMeta: metav1.ObjectMeta{Name: instance.Name}} @@ -133,11 +135,11 @@ func (r *WorkspaceSubroutine) Process(ctx context.Context, ro runtimeobject.Runt return controllerutil.SetOwnerReference(instance, createdWorkspace, clusterClient.Scheme()) }); err != nil { - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + return subroutines.OK(), err } r.limiter.Forget(instance) - return ctrl.Result{}, nil + return subroutines.OK(), nil } // TODO: could potentially work without the orgsClient when we look up the orgs workspaceid on startup diff --git a/pkg/subroutines/workspace/workspace_test.go b/pkg/subroutines/workspace/workspace_test.go index 1686afc..e9ede3a 100644 --- a/pkg/subroutines/workspace/workspace_test.go +++ b/pkg/subroutines/workspace/workspace_test.go @@ -10,7 +10,6 @@ import ( corev1alpha1 "github.com/platform-mesh/account-operator/api/v1alpha1" "github.com/platform-mesh/account-operator/pkg/subroutines/mocks" "github.com/platform-mesh/account-operator/pkg/subroutines/workspace" - "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" v1 "k8s.io/api/core/v1" @@ -36,7 +35,7 @@ func TestFinalizers(t *testing.T) { func TestFinalize(t *testing.T) { testCases := []struct { name string - obj runtimeobject.RuntimeObject + obj client.Object k8sMocks func(m *mocks.Client) expectRequeue bool }{ @@ -114,9 +113,9 @@ func TestFinalize(t *testing.T) { ctx = mccontext.WithCluster(ctx, "test") result, err := s.Finalize(ctx, test.obj) - assert.Nil(t, err) + assert.NoError(t, err) if test.expectRequeue { - assert.Greater(t, result.RequeueAfter.Microseconds(), int64(0)) + assert.Greater(t, result.Requeue().Microseconds(), int64(0)) } }) } @@ -130,14 +129,14 @@ func TestProcess(t *testing.T) { testCases := []struct { name string - obj runtimeobject.RuntimeObject + obj client.Object k8sMocks func(m *mocks.Client) orgsK8sMocks func(m *mocks.Client) expectRequeue bool expectError bool }{ { - name: "shuold create workspace if not exists", + name: "should create workspace if not exists", obj: &corev1alpha1.Account{ ObjectMeta: metav1.ObjectMeta{ Name: "test", @@ -290,12 +289,12 @@ func TestProcess(t *testing.T) { result, err := s.Process(ctx, test.obj) if test.expectError { - assert.Error(t, err.Err()) + assert.Error(t, err) } else { - assert.Nil(t, err) + assert.NoError(t, err) } if test.expectRequeue { - assert.Greater(t, result.RequeueAfter.Microseconds(), int64(0)) + assert.Greater(t, result.Requeue().Microseconds(), int64(0)) } }) } diff --git a/pkg/subroutines/workspaceready/workspaceready.go b/pkg/subroutines/workspaceready/workspaceready.go index 3435d9d..ea56c69 100644 --- a/pkg/subroutines/workspaceready/workspaceready.go +++ b/pkg/subroutines/workspaceready/workspaceready.go @@ -3,15 +3,12 @@ package workspaceready import ( "context" "fmt" + "time" kcpcorev1alpha "github.com/kcp-dev/sdk/apis/core/v1alpha1" kcptenancyv1alpha "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" - "github.com/platform-mesh/golang-commons/controller/lifecycle/ratelimiter" - "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/platform-mesh/subroutines" "k8s.io/client-go/util/workqueue" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" @@ -19,7 +16,7 @@ import ( "github.com/platform-mesh/account-operator/pkg/clusteredname" ) -var _ subroutine.Subroutine = (*WorkspaceReadySubroutine)(nil) +var _ subroutines.Processor = (*WorkspaceReadySubroutine)(nil) const ( WorkspaceReadySubroutineName = "WorkspaceReadySubroutine" @@ -37,42 +34,35 @@ type WorkspaceReadySubroutine struct { // New returns a new WorkspaceReadySubroutine. func New(mgr mcmanager.Manager) *WorkspaceReadySubroutine { - limiter, _ := ratelimiter.NewStaticThenExponentialRateLimiter[*v1alpha1.Account](ratelimiter.NewConfig()) //nolint:errcheck - - return &WorkspaceReadySubroutine{mgr: mgr, limiter: limiter} + return &WorkspaceReadySubroutine{ + mgr: mgr, + limiter: workqueue.NewTypedItemExponentialFailureRateLimiter[*v1alpha1.Account](1*time.Second, 120*time.Second), + } } func (r *WorkspaceReadySubroutine) GetName() string { return WorkspaceReadySubroutineName } -func (r *WorkspaceReadySubroutine) Finalizers(_ runtimeobject.RuntimeObject) []string { - return []string{} -} - -func (r *WorkspaceReadySubroutine) Finalize(_ context.Context, _ runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { - return ctrl.Result{}, nil -} - -func (r *WorkspaceReadySubroutine) Process(ctx context.Context, ro runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { - instance := ro.(*v1alpha1.Account) - cn := clusteredname.MustGetClusteredName(ctx, ro) +func (r *WorkspaceReadySubroutine) Process(ctx context.Context, obj client.Object) (subroutines.Result, error) { + instance := obj.(*v1alpha1.Account) + cn := clusteredname.MustGetClusteredName(ctx, obj) clusterRef, err := r.mgr.GetCluster(ctx, cn.ClusterID.String()) if err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("getting cluster: %w", err), true, true) + return subroutines.OK(), fmt.Errorf("getting cluster: %w", err) } clusterClient := clusterRef.GetClient() ws := &kcptenancyv1alpha.Workspace{} if err := clusterClient.Get(ctx, client.ObjectKey{Name: instance.Name}, ws); err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("getting Account's Workspace: %w", err), true, true) + return subroutines.OK(), fmt.Errorf("getting Account's Workspace: %w", err) } if ws.Status.Phase != kcpcorev1alpha.LogicalClusterPhaseReady { - return ctrl.Result{RequeueAfter: r.limiter.When(instance)}, nil + return subroutines.OKWithRequeue(r.limiter.When(instance)), nil } r.limiter.Forget(instance) - return ctrl.Result{}, nil + return subroutines.OK(), nil } diff --git a/pkg/subroutines/workspaceready/workspaceready_test.go b/pkg/subroutines/workspaceready/workspaceready_test.go index dc77959..1ee05dd 100644 --- a/pkg/subroutines/workspaceready/workspaceready_test.go +++ b/pkg/subroutines/workspaceready/workspaceready_test.go @@ -10,7 +10,6 @@ import ( corev1alpha1 "github.com/platform-mesh/account-operator/api/v1alpha1" "github.com/platform-mesh/account-operator/pkg/subroutines/mocks" "github.com/platform-mesh/account-operator/pkg/subroutines/workspaceready" - "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" kerrors "k8s.io/apimachinery/pkg/api/errors" @@ -23,7 +22,7 @@ import ( func TestProcess(t *testing.T) { testCases := []struct { name string - obj runtimeobject.RuntimeObject + obj client.Object k8sMocks func(m *mocks.Client) expectRequeue bool expectError bool @@ -103,12 +102,12 @@ func TestProcess(t *testing.T) { result, err := s.Process(ctx, test.obj) if test.expectError { - assert.Error(t, err.Err()) + assert.Error(t, err) } else { - assert.Nil(t, err) + assert.NoError(t, err) } if test.expectRequeue { - assert.Greater(t, result.RequeueAfter.Microseconds(), int64(0)) + assert.Greater(t, result.Requeue().Microseconds(), int64(0)) } }) } diff --git a/pkg/subroutines/workspacetype/workspace_type.go b/pkg/subroutines/workspacetype/workspace_type.go index 1889396..8388165 100644 --- a/pkg/subroutines/workspacetype/workspace_type.go +++ b/pkg/subroutines/workspacetype/workspace_type.go @@ -5,15 +5,12 @@ import ( "maps" kcptenancyv1alpha "github.com/kcp-dev/sdk/apis/tenancy/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/platform-mesh/golang-commons/logger" + "github.com/platform-mesh/subroutines" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" "github.com/platform-mesh/account-operator/api/v1alpha1" "github.com/platform-mesh/account-operator/pkg/subroutines/util" @@ -30,7 +27,10 @@ const ( orgsWorkspacePath = "root:orgs" ) -var _ subroutine.Subroutine = &WorkspaceTypeSubroutine{} +var ( + _ subroutines.Processor = &WorkspaceTypeSubroutine{} + _ subroutines.Finalizer = &WorkspaceTypeSubroutine{} +) type WorkspaceTypeSubroutine struct { orgsClient client.Client @@ -42,12 +42,16 @@ func New(orgsClient client.Client) *WorkspaceTypeSubroutine { } } -func (w *WorkspaceTypeSubroutine) Process(ctx context.Context, ro runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { - instance := ro.(*v1alpha1.Account) - log := logger.LoadLoggerFromContext(ctx) +func (w *WorkspaceTypeSubroutine) GetName() string { + return SubroutineName +} + +func (w *WorkspaceTypeSubroutine) Process(ctx context.Context, obj client.Object) (subroutines.Result, error) { + instance := obj.(*v1alpha1.Account) + logger := log.FromContext(ctx) if instance.Spec.Type != v1alpha1.AccountTypeOrg { - return ctrl.Result{}, nil + return subroutines.OK(), nil } orgWorkspaceTypeName := util.GetWorkspaceTypeName(instance.Name, instance.Spec.Type) @@ -57,16 +61,16 @@ func (w *WorkspaceTypeSubroutine) Process(ctx context.Context, ro runtimeobject. accWst := generateAccountWorkspaceType(orgWorkspaceTypeName, accountWorkspaceTypeName, instance.Name) if err := w.createOrPatchWorkspaceType(ctx, orgWst); err != nil { // coverage-ignore - log.Error().Err(err).Str("name", orgWst.Name).Msg("failed to create or update org workspace type") - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + logger.Error(err, "failed to create or update org workspace type", "name", orgWst.Name) + return subroutines.OK(), err } if err := w.createOrPatchWorkspaceType(ctx, accWst); err != nil { // coverage-ignore - log.Error().Err(err).Str("name", accWst.Name).Msg("failed to create or update account workspace type") - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + logger.Error(err, "failed to create or update account workspace type", "name", accWst.Name) + return subroutines.OK(), err } - return ctrl.Result{}, nil + return subroutines.OK(), nil } func (w *WorkspaceTypeSubroutine) createOrPatchWorkspaceType(ctx context.Context, desiredWst kcptenancyv1alpha.WorkspaceType) error { @@ -86,11 +90,11 @@ func (w *WorkspaceTypeSubroutine) createOrPatchWorkspaceType(ctx context.Context return err } -func (w *WorkspaceTypeSubroutine) Finalize(ctx context.Context, ro runtimeobject.RuntimeObject) (ctrl.Result, errors.OperatorError) { - instance := ro.(*v1alpha1.Account) - log := logger.LoadLoggerFromContext(ctx) +func (w *WorkspaceTypeSubroutine) Finalize(ctx context.Context, obj client.Object) (subroutines.Result, error) { + instance := obj.(*v1alpha1.Account) + logger := log.FromContext(ctx) if instance.Spec.Type != v1alpha1.AccountTypeOrg { - return ctrl.Result{}, nil + return subroutines.OK(), nil } orgWorkspaceTypeName := util.GetWorkspaceTypeName(instance.Name, instance.Spec.Type) @@ -98,26 +102,22 @@ func (w *WorkspaceTypeSubroutine) Finalize(ctx context.Context, ro runtimeobject if err := w.orgsClient.Delete(ctx, &kcptenancyv1alpha.WorkspaceType{ObjectMeta: metav1.ObjectMeta{Name: orgWorkspaceTypeName}}); err != nil { if !kerrors.IsNotFound(err) { - log.Error().Err(err).Str("name", orgWorkspaceTypeName).Msg("failed to delete org workspace type") - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + logger.Error(err, "failed to delete org workspace type", "name", orgWorkspaceTypeName) + return subroutines.OK(), err } } if err := w.orgsClient.Delete(ctx, &kcptenancyv1alpha.WorkspaceType{ObjectMeta: metav1.ObjectMeta{Name: accountWorkspaceTypeName}}); err != nil { if !kerrors.IsNotFound(err) { // coverage-ignore - log.Error().Err(err).Str("name", accountWorkspaceTypeName).Msg("failed to delete account workspace type") - return ctrl.Result{}, errors.NewOperatorError(err, true, true) + logger.Error(err, "failed to delete account workspace type", "name", accountWorkspaceTypeName) + return subroutines.OK(), err } } - return ctrl.Result{}, nil -} - -func (w *WorkspaceTypeSubroutine) GetName() string { - return SubroutineName + return subroutines.OK(), nil } -func (w *WorkspaceTypeSubroutine) Finalizers(obj runtimeobject.RuntimeObject) []string { +func (w *WorkspaceTypeSubroutine) Finalizers(obj client.Object) []string { account := obj.(*v1alpha1.Account) if account.Spec.Type != v1alpha1.AccountTypeOrg { return []string{} diff --git a/pkg/subroutines/workspacetype/workspace_type_test.go b/pkg/subroutines/workspacetype/workspace_type_test.go index 9879978..901a687 100644 --- a/pkg/subroutines/workspacetype/workspace_type_test.go +++ b/pkg/subroutines/workspacetype/workspace_type_test.go @@ -5,7 +5,6 @@ import ( "testing" kcptenancyv1alpha "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" - "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -34,7 +33,7 @@ func TestFinalizer(t *testing.T) { func TestFinalize(t *testing.T) { testCases := []struct { name string - obj runtimeobject.RuntimeObject + obj client.Object k8sMocks func(client *mocks.Client) expectError bool }{ @@ -104,9 +103,9 @@ func TestFinalize(t *testing.T) { _, err := s.Finalize(ctx, test.obj) if test.expectError { - assert.Error(t, err.Err()) + assert.Error(t, err) } else { - assert.Nil(t, err) + assert.NoError(t, err) } }) @@ -116,12 +115,12 @@ func TestFinalize(t *testing.T) { func TestProcess(t *testing.T) { testCases := []struct { name string - obj runtimeobject.RuntimeObject + obj client.Object k8sMocks func(client *mocks.Client) expectError bool }{ { - name: "", + name: "should create both workspace types for org account", obj: &v1alpha1.Account{ ObjectMeta: metav1.ObjectMeta{ Name: "test", @@ -154,9 +153,9 @@ func TestProcess(t *testing.T) { _, err := s.Process(t.Context(), test.obj) if test.expectError { - assert.Error(t, err.Err()) + assert.Error(t, err) } else { - assert.Nil(t, err) + assert.NoError(t, err) } }) } @@ -196,7 +195,7 @@ func TestProcess_PreservesAuthenticationConfigurations(t *testing.T) { } _, err := s.Process(t.Context(), account) - require.Nil(t, err) + require.NoError(t, err) updatedOrgWst := &kcptenancyv1alpha.WorkspaceType{} require.NoError(t, fakeClient.Get(t.Context(), client.ObjectKey{Name: "test-org"}, updatedOrgWst))