From 7a3a99cecbe9c9c2c5a37d97c83f3801aa23a3c1 Mon Sep 17 00:00:00 2001 From: hongming Date: Fri, 20 Dec 2024 16:40:01 +0800 Subject: [PATCH 1/4] fix workspace role name exceeding the length limit (#2132) Signed-off-by: hongming --- .../namespace/namespace_controller.go | 38 +++++-- .../workspace/workspace_controller.go | 64 +++++++---- .../workspace_controller_suite_test.go | 10 +- .../workspace/workspace_controller_test.go | 104 +++++++++++++++--- .../workspacetemplate_controller.go | 36 +++--- pkg/kapis/tenant/v1beta1/handler.go | 29 ++--- 6 files changed, 193 insertions(+), 88 deletions(-) diff --git a/pkg/controller/namespace/namespace_controller.go b/pkg/controller/namespace/namespace_controller.go index 31d27b340..4fba446d9 100644 --- a/pkg/controller/namespace/namespace_controller.go +++ b/pkg/controller/namespace/namespace_controller.go @@ -11,6 +11,7 @@ import ( "fmt" "github.com/go-logr/logr" + "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -76,7 +77,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu if controllerutil.ContainsFinalizer(namespace, constants.CascadingDeletionFinalizer) { controllerutil.RemoveFinalizer(namespace, constants.CascadingDeletionFinalizer) if err := r.Update(ctx, namespace); err != nil { - return ctrl.Result{}, fmt.Errorf("failed to remove finalizer: %v", err) + return ctrl.Result{}, errors.Wrapf(err, "failed to remove finalizer") } } // Our finalizer has finished, so the reconciler can do nothing. @@ -89,7 +90,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // then lets add the finalizer and update the object. if workspaceLabelExists && !controllerutil.ContainsFinalizer(namespace, constants.CascadingDeletionFinalizer) { if err := r.initCreatorRoleBinding(ctx, namespace); err != nil { - return ctrl.Result{}, fmt.Errorf("failed to init creator role binding: %v", err) + return ctrl.Result{}, errors.Wrapf(err, "failed to init creator role binding") } updated := namespace.DeepCopy() // Remove legacy finalizer @@ -97,11 +98,20 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // Remove legacy ownerReferences updated.OwnerReferences = make([]metav1.OwnerReference, 0) controllerutil.AddFinalizer(updated, constants.CascadingDeletionFinalizer) - return ctrl.Result{}, r.Patch(ctx, updated, client.MergeFrom(namespace)) + if err := r.Patch(ctx, updated, client.MergeFrom(namespace)); err != nil { + return ctrl.Result{}, errors.Wrapf(err, "failed to add finalizer") + } + return ctrl.Result{}, nil } - if err := r.initRoles(ctx, namespace); err != nil { - return ctrl.Result{}, fmt.Errorf("failed to init builtin roles: %v", err) + if workspaceLabelExists { + if err := r.initRoles(ctx, namespace); err != nil { + return ctrl.Result{}, errors.Wrapf(err, "failed to init roles in namespace %s", namespace.Name) + } + } else { + if err := r.cleanUp(ctx, namespace); err != nil { + return ctrl.Result{}, errors.Wrapf(err, "failed to clean up namespace %s", namespace.Name) + } } r.recorder.Event(namespace, corev1.EventTypeNormal, kscontroller.Synced, kscontroller.MessageResourceSynced) @@ -112,7 +122,7 @@ func (r *Reconciler) initRoles(ctx context.Context, namespace *corev1.Namespace) logger := klog.FromContext(ctx) var templates iamv1beta1.BuiltinRoleList if err := r.List(ctx, &templates, client.MatchingLabels{iamv1beta1.ScopeLabel: iamv1beta1.ScopeNamespace}); err != nil { - return fmt.Errorf("failed to list builtin roles: %v", err) + return errors.Wrapf(err, "failed to list builtin role templates") } for _, template := range templates.Items { selector, err := metav1.LabelSelectorAsSelector(&template.TargetSelector) @@ -135,7 +145,7 @@ func (r *Reconciler) initRoles(ctx context.Context, namespace *corev1.Namespace) return nil }) if err != nil { - return fmt.Errorf("failed to create or update builtin role: %v", err) + return errors.Wrapf(err, "failed to create or update builtin role") } logger.V(4).Info("builtin role initialized", "operation", op) } else if err != nil { @@ -176,8 +186,20 @@ func (r *Reconciler) initCreatorRoleBinding(ctx context.Context, namespace *core return nil }) if err != nil { - return err + return errors.Wrapf(err, "failed to create or update creator role binding") } klog.FromContext(ctx).V(4).Info("creator role binding initialized", "operation", op) return nil } + +func (r *Reconciler) cleanUp(ctx context.Context, namespace *corev1.Namespace) error { + role := &iamv1beta1.Role{} + if err := r.DeleteAllOf(ctx, role, client.InNamespace(namespace.Name)); err != nil { + return errors.Wrapf(err, "failed to delete roles") + } + roleBinding := &iamv1beta1.RoleBinding{} + if err := r.DeleteAllOf(ctx, roleBinding, client.InNamespace(namespace.Name)); err != nil { + return errors.Wrapf(err, "failed to delete role bindings") + } + return nil +} diff --git a/pkg/controller/workspace/workspace_controller.go b/pkg/controller/workspace/workspace_controller.go index 638ee783a..6255795f3 100644 --- a/pkg/controller/workspace/workspace_controller.go +++ b/pkg/controller/workspace/workspace_controller.go @@ -7,16 +7,14 @@ package workspace import ( "context" - "fmt" - - "kubesphere.io/kubesphere/pkg/constants" - - apierrors "k8s.io/apimachinery/pkg/api/errors" "github.com/go-logr/logr" + "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/retry" "k8s.io/klog/v2" tenantv1beta1 "kubesphere.io/api/tenant/v1beta1" ctrl "sigs.k8s.io/controller-runtime" @@ -25,6 +23,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "kubesphere.io/kubesphere/pkg/constants" kscontroller "kubesphere.io/kubesphere/pkg/controller" ) @@ -76,7 +75,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu controllerutil.RemoveFinalizer(expected, "finalizers.tenant.kubesphere.io") controllerutil.AddFinalizer(expected, constants.CascadingDeletionFinalizer) if err := r.Patch(ctx, expected, client.MergeFrom(workspace)); err != nil { - return ctrl.Result{}, fmt.Errorf("failed to add finalizer: %s", err) + return ctrl.Result{}, errors.Wrapf(err, "failed to add finalizer to workspace %s", workspace.Name) } workspaceOperation.WithLabelValues("create", workspace.Name).Inc() } @@ -84,12 +83,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu if controllerutil.ContainsFinalizer(workspace, constants.CascadingDeletionFinalizer) { ok, err := r.workspaceCascadingDeletion(ctx, workspace) if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to delete workspace: %s", err) + return ctrl.Result{}, errors.Wrapf(err, "failed to delete workspace %s", workspace.Name) } if ok { controllerutil.RemoveFinalizer(workspace, constants.CascadingDeletionFinalizer) if err := r.Update(ctx, workspace); err != nil { - return ctrl.Result{}, fmt.Errorf("failed to remove finalizer: %s", err) + return ctrl.Result{}, errors.Wrapf(err, "failed to remove finalizer from workspace %s", workspace.Name) } workspaceOperation.WithLabelValues("delete", workspace.Name).Inc() } @@ -106,19 +105,42 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // It returns a boolean indicating whether the deletion was successful and an error if any occurred. func (r *Reconciler) workspaceCascadingDeletion(ctx context.Context, workspace *tenantv1beta1.Workspace) (bool, error) { switch workspace.Annotations[constants.DeletionPropagationAnnotation] { - case string(metav1.DeletePropagationOrphan): - // If the deletion propagation policy is "Orphan", return true without deleting namespaces. - return true, nil - case string(metav1.DeletePropagationForeground), string(metav1.DeletePropagationBackground): - // If the deletion propagation policy is "Foreground" or "Background", delete the namespaces. - if err := r.deleteNamespaces(ctx, workspace); err != nil { - return false, fmt.Errorf("failed to delete namespaces in workspace %s: %s", workspace.Name, err) + case string(metav1.DeletePropagationOrphan), "": + if err := r.cleanUpNamespaces(ctx, workspace); err != nil { + return false, errors.Wrapf(err, "failed to clean up namespaces in workspace %s", workspace.Name) + } + case string(metav1.DeletePropagationForeground), string(metav1.DeletePropagationBackground): + if err := r.deleteNamespaces(ctx, workspace); err != nil { + return false, errors.Wrapf(err, "failed to delete namespaces in workspace %s", workspace.Name) } - return true, nil - default: - // If the deletion propagation policy is invalid, return an error. - return false, fmt.Errorf("invalid deletion propagation policy: %s", workspace.Annotations[constants.DeletionPropagationAnnotation]) } + return true, nil +} + +func (r *Reconciler) cleanUpNamespaces(ctx context.Context, workspace *tenantv1beta1.Workspace) error { + namespaces := &corev1.NamespaceList{} + if err := r.List(ctx, namespaces, client.MatchingLabels{tenantv1beta1.WorkspaceLabel: workspace.Name}); err != nil { + return errors.Wrapf(err, "failed to list namespaces in workspace %s", workspace.Name) + } + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + for _, ns := range namespaces.Items { + if err := r.Get(ctx, client.ObjectKeyFromObject(&ns), &ns); err != nil { + if apierrors.IsNotFound(err) { + continue + } + return err + } + delete(ns.Labels, tenantv1beta1.WorkspaceLabel) + if err := r.Update(ctx, &ns); err != nil { + return err + } + } + return nil + }) + if err != nil { + return errors.Wrapf(err, "failed to clean up namespaces in workspace %s", workspace.Name) + } + return nil } // deleteNamespaces deletes all namespaces associated with the given workspace. @@ -126,14 +148,14 @@ func (r *Reconciler) workspaceCascadingDeletion(ctx context.Context, workspace * func (r *Reconciler) deleteNamespaces(ctx context.Context, workspace *tenantv1beta1.Workspace) error { namespaces := &corev1.NamespaceList{} if err := r.List(ctx, namespaces, client.MatchingLabels{tenantv1beta1.WorkspaceLabel: workspace.Name}); err != nil { - return fmt.Errorf("failed to list namespaces in workspace %s: %s", workspace.Name, err) + return errors.Wrapf(err, "failed to list namespaces in workspace %s", workspace.Name) } for _, ns := range namespaces.Items { if err := r.Delete(ctx, &ns); err != nil { if apierrors.IsNotFound(err) { continue } - return fmt.Errorf("failed to delete namespace %s: %s", ns.Name, err) + return errors.Wrapf(err, "failed to delete namespace %s in workspace %s", ns.Name, workspace.Name) } } return nil diff --git a/pkg/controller/workspace/workspace_controller_suite_test.go b/pkg/controller/workspace/workspace_controller_suite_test.go index 32e712c94..eddb08dc6 100644 --- a/pkg/controller/workspace/workspace_controller_suite_test.go +++ b/pkg/controller/workspace/workspace_controller_suite_test.go @@ -11,19 +11,18 @@ import ( "testing" "time" - "kubesphere.io/kubesphere/pkg/controller/controllertest" - - kscontroller "kubesphere.io/kubesphere/pkg/controller" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/klog/v2" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + kscontroller "kubesphere.io/kubesphere/pkg/controller" + "kubesphere.io/kubesphere/pkg/controller/controllertest" "kubesphere.io/kubesphere/pkg/scheme" ) @@ -45,10 +44,9 @@ var _ = BeforeSuite(func() { logf.SetLogger(klog.NewKlogr()) By("bootstrapping test environment") - t := true if os.Getenv("TEST_USE_EXISTING_CLUSTER") == "true" { testEnv = &envtest.Environment{ - UseExistingCluster: &t, + UseExistingCluster: ptr.To(true), } } else { crdDirPaths, err := controllertest.LoadCrdPath() diff --git a/pkg/controller/workspace/workspace_controller_test.go b/pkg/controller/workspace/workspace_controller_test.go index dd9ae9a47..0861bb1f2 100644 --- a/pkg/controller/workspace/workspace_controller_test.go +++ b/pkg/controller/workspace/workspace_controller_test.go @@ -9,17 +9,18 @@ import ( "context" "time" - "kubesphere.io/kubesphere/pkg/constants" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" tenantv1beta1 "kubesphere.io/api/tenant/v1beta1" + + "kubesphere.io/kubesphere/pkg/constants" ) var _ = Describe("Workspace", func() { - const timeout = time.Second * 30 const interval = time.Second * 1 @@ -28,16 +29,24 @@ var _ = Describe("Workspace", func() { // Avoid adding tests for vanilla CRUD operations because they would // test Kubernetes API server, which isn't the goal here. Context("Workspace Controller", func() { - It("Should create successfully", func() { - + It("DeletePropagationBackground", func() { workspace := &tenantv1beta1.Workspace{ ObjectMeta: metav1.ObjectMeta{ - Name: "workspace-test", + Name: "workspace-test1", + }, + } + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "namespace-test1", + Labels: map[string]string{ + tenantv1beta1.WorkspaceLabel: workspace.Name, + }, }, } // Create Expect(k8sClient.Create(context.Background(), workspace)).Should(Succeed()) + Expect(k8sClient.Create(context.Background(), namespace)).Should(Succeed()) By("Expecting to create workspace successfully") Eventually(func() bool { @@ -58,15 +67,6 @@ var _ = Describe("Workspace", func() { return workspace.Spec.Manager == "admin" }, timeout, interval).Should(BeTrue()) - // Delete - By("Expecting to delete workspace successfully") - Eventually(func() error { - if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: workspace.Name}, workspace); err != nil { - return err - } - return k8sClient.Delete(context.Background(), workspace) - }, timeout, interval).Should(Succeed()) - // Update DeletionPropagation By("Expecting to delete workspace successfully") Eventually(func() error { @@ -80,10 +80,78 @@ var _ = Describe("Workspace", func() { return k8sClient.Update(context.Background(), workspace) }, timeout, interval).Should(Succeed()) - By("Expecting to delete workspace finish") + // Delete workspace + By("Expecting to delete workspace successfully") Eventually(func() error { - return k8sClient.Get(context.Background(), types.NamespacedName{Name: workspace.Name}, workspace) - }, timeout, interval).ShouldNot(Succeed()) + return k8sClient.Delete(context.Background(), workspace) + }, timeout, interval).Should(Succeed()) + + By("Expecting to cascading deletion finish") + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), types.NamespacedName{Name: namespace.Name}, namespace) + // Deleting a namespace will seem to succeed, but the namespace will just be put in a Terminating state, and never actually be reclaimed. + // Reference: https://book.kubebuilder.io/reference/envtest.html#namespace-usage-limitation + return namespace.Status.Phase == corev1.NamespaceTerminating + }, timeout, interval).Should(BeTrue()) + + By("Expecting to delete workspace finish") + Eventually(func() bool { + return apierrors.IsNotFound(k8sClient.Get(context.Background(), types.NamespacedName{Name: workspace.Name}, workspace)) + }, timeout, interval).Should(BeTrue()) + }) + It("DeletePropagationOrphan", func() { + workspace := &tenantv1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "workspace-test2", + }, + } + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "namespace-test2", + Labels: map[string]string{ + tenantv1beta1.WorkspaceLabel: workspace.Name, + }, + }, + } + // Create + Expect(k8sClient.Create(context.Background(), workspace)).Should(Succeed()) + Expect(k8sClient.Create(context.Background(), namespace)).Should(Succeed()) + + By("Expecting to create workspace successfully") + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), types.NamespacedName{Name: workspace.Name}, workspace) + return len(workspace.Finalizers) > 0 + }, timeout, interval).Should(BeTrue()) + + // Update + updated := &tenantv1beta1.Workspace{} + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: workspace.Name}, updated)).Should(Succeed()) + updated.Spec.Manager = "admin" + Expect(k8sClient.Update(context.Background(), updated)).Should(Succeed()) + + // List workspace role bindings + By("Expecting to update workspace successfully") + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), types.NamespacedName{Name: workspace.Name}, workspace) + return workspace.Spec.Manager == "admin" + }, timeout, interval).Should(BeTrue()) + + // Delete workspace + By("Expecting to delete workspace successfully") + Eventually(func() error { + return k8sClient.Delete(context.Background(), workspace) + }, timeout, interval).Should(Succeed()) + + By("Expecting to delete workspace finish") + Eventually(func() bool { + return apierrors.IsNotFound(k8sClient.Get(context.Background(), types.NamespacedName{Name: workspace.Name}, workspace)) + }, timeout, interval).Should(BeTrue()) + + By("Expecting to cascading deletion finish") + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), types.NamespacedName{Name: namespace.Name}, namespace) + return namespace.Labels[tenantv1beta1.WorkspaceLabel] == "" && namespace.Status.Phase != corev1.NamespaceTerminating + }, timeout, interval).Should(BeTrue()) }) }) }) diff --git a/pkg/controller/workspacetemplate/workspacetemplate_controller.go b/pkg/controller/workspacetemplate/workspacetemplate_controller.go index 7b0890958..36232cb4a 100644 --- a/pkg/controller/workspacetemplate/workspacetemplate_controller.go +++ b/pkg/controller/workspacetemplate/workspacetemplate_controller.go @@ -11,14 +11,13 @@ import ( "fmt" "strings" - "kubesphere.io/kubesphere/pkg/constants" - "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" @@ -33,11 +32,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "kubesphere.io/kubesphere/pkg/constants" kscontroller "kubesphere.io/kubesphere/pkg/controller" "kubesphere.io/kubesphere/pkg/controller/cluster/predicate" clusterutils "kubesphere.io/kubesphere/pkg/controller/cluster/utils" "kubesphere.io/kubesphere/pkg/controller/workspacetemplate/utils" "kubesphere.io/kubesphere/pkg/utils/clusterclient" + "kubesphere.io/kubesphere/pkg/utils/hashutil" ) const ( @@ -231,7 +232,7 @@ func (r *Reconciler) initWorkspaceRoles(ctx context.Context, workspaceTemplate * builtinWorkspaceRole.Kind == iamv1beta1.ResourceKindWorkspaceRole { target := &iamv1beta1.WorkspaceRole{ ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-%s", workspaceTemplate.Name, builtinWorkspaceRole.Name), + Name: ensureWorkspaceRoleName(workspaceTemplate.Name, builtinWorkspaceRole.Name), }, } op, err := controllerutil.CreateOrUpdate(ctx, r.Client, target, func() error { @@ -256,12 +257,21 @@ func (r *Reconciler) initWorkspaceRoles(ctx context.Context, workspaceTemplate * return nil } +func ensureWorkspaceRoleName(workspace, role string) string { + workspaceRoleName := fmt.Sprintf("%s-%s", workspace, role) + if len(workspaceRoleName) <= validation.LabelValueMaxLength { + return workspaceRoleName + } + hashedWorkspaceName := hashutil.FNVString([]byte(workspace)) + return fmt.Sprintf("%s.%s", role, hashedWorkspaceName) +} + func (r *Reconciler) initManagerRoleBinding(ctx context.Context, workspaceTemplate *tenantv1beta1.WorkspaceTemplate) error { manager := workspaceTemplate.Spec.Template.Spec.Manager if manager == "" { return nil } - workspaceAdminRoleName := fmt.Sprintf("%s-admin", workspaceTemplate.Name) + workspaceAdminRoleName := ensureWorkspaceRoleName(workspaceTemplate.Name, "admin") existWorkspaceRoleBinding := &iamv1beta1.WorkspaceRoleBinding{ObjectMeta: metav1.ObjectMeta{Name: workspaceAdminRoleName}} if _, err := ctrl.CreateOrUpdate(ctx, r.Client, existWorkspaceRoleBinding, func() error { existWorkspaceRoleBinding.Labels = map[string]string{ @@ -269,7 +279,6 @@ func (r *Reconciler) initManagerRoleBinding(ctx context.Context, workspaceTempla iamv1beta1.UserReferenceLabel: manager, iamv1beta1.RoleReferenceLabel: workspaceAdminRoleName, } - existWorkspaceRoleBinding.RoleRef = rbacv1.RoleRef{ APIGroup: iamv1beta1.SchemeGroupVersion.Group, Kind: iamv1beta1.ResourceKindWorkspaceRole, @@ -290,13 +299,6 @@ func (r *Reconciler) initManagerRoleBinding(ctx context.Context, workspaceTempla } func (r *Reconciler) workspaceTemplateCascadingDeletion(ctx context.Context, workspaceTemplate *tenantv1beta1.WorkspaceTemplate) (bool, error) { - switch workspaceTemplate.Annotations[constants.DeletionPropagationAnnotation] { - case string(metav1.DeletePropagationOrphan), string(metav1.DeletePropagationForeground), string(metav1.DeletePropagationBackground): - default: - klog.FromContext(ctx).V(4).Info(fmt.Sprintf("waiting for deletion propagation update, invalid deletion propagation policy found: %s", workspaceTemplate.Annotations[constants.DeletionPropagationAnnotation])) - return false, nil - } - clusters, err := r.clusterClientSet.ListClusters(ctx) if err != nil { return false, fmt.Errorf("failed to list clusters: %s", err) @@ -330,12 +332,7 @@ func (r *Reconciler) workspaceCascadingDeletion(ctx context.Context, clusterName if err := clusterClient.Get(ctx, types.NamespacedName{Name: workspaceTemplate.Name}, workspace); err != nil { return client.IgnoreNotFound(err) } - if workspace.DeletionTimestamp.IsZero() { - if err := clusterClient.Delete(ctx, workspace); err != nil { - return fmt.Errorf("failed to delete workspace %s in cluster %s: %s", workspace.Name, clusterName, err) - } - } - if workspace.Annotations[constants.DeletionPropagationAnnotation] == workspaceTemplate.Annotations[constants.DeletionPropagationAnnotation] { + if !workspace.DeletionTimestamp.IsZero() { return nil } if workspace.Annotations == nil { @@ -345,5 +342,8 @@ func (r *Reconciler) workspaceCascadingDeletion(ctx context.Context, clusterName if err := clusterClient.Update(ctx, workspace); err != nil { return fmt.Errorf("failed to update workspace %s in cluster %s: %s", workspace.Name, clusterName, err) } + if err := clusterClient.Delete(ctx, workspace); err != nil { + return fmt.Errorf("failed to delete workspace %s in cluster %s: %s", workspace.Name, clusterName, err) + } return nil } diff --git a/pkg/kapis/tenant/v1beta1/handler.go b/pkg/kapis/tenant/v1beta1/handler.go index f84c69793..ea392a64d 100644 --- a/pkg/kapis/tenant/v1beta1/handler.go +++ b/pkg/kapis/tenant/v1beta1/handler.go @@ -48,7 +48,7 @@ func (h *handler) ListWorkspaces(req *restful.Request, resp *restful.Response) { return } - resp.WriteEntity(result) + _ = resp.WriteEntity(result) } func (h *handler) GetWorkspace(request *restful.Request, response *restful.Response) { @@ -63,7 +63,7 @@ func (h *handler) GetWorkspace(request *restful.Request, response *restful.Respo return } - response.WriteEntity(workspace) + _ = response.WriteEntity(workspace) } func (h *handler) CreateWorkspaceTemplate(req *restful.Request, resp *restful.Response) { @@ -99,23 +99,18 @@ func (h *handler) CreateWorkspaceTemplate(req *restful.Request, resp *restful.Re return } - resp.WriteEntity(created) + _ = resp.WriteEntity(created) } func (h *handler) DeleteWorkspaceTemplate(request *restful.Request, response *restful.Response) { workspace := request.PathParameter("workspace") - opts := metav1.DeleteOptions{} - - err := request.ReadEntity(&opts) - if err != nil { - opts = *metav1.NewDeleteOptions(0) + if err := request.ReadEntity(&opts); err != nil { + api.HandleBadRequest(response, request, err) + return } - err = h.tenant.DeleteWorkspaceTemplate(workspace, opts) - - if err != nil { - klog.Error(err) + if err := h.tenant.DeleteWorkspaceTemplate(workspace, opts); err != nil { if errors.IsNotFound(err) { api.HandleNotFound(response, request, err) return @@ -124,7 +119,7 @@ func (h *handler) DeleteWorkspaceTemplate(request *restful.Request, response *re return } - response.WriteEntity(servererr.None) + _ = response.WriteEntity(servererr.None) } func (h *handler) UpdateWorkspaceTemplate(req *restful.Request, resp *restful.Response) { @@ -173,7 +168,7 @@ func (h *handler) UpdateWorkspaceTemplate(req *restful.Request, resp *restful.Re return } - resp.WriteEntity(updated) + _ = resp.WriteEntity(updated) } func (h *handler) DescribeWorkspaceTemplate(request *restful.Request, response *restful.Response) { @@ -187,7 +182,7 @@ func (h *handler) DescribeWorkspaceTemplate(request *restful.Request, response * api.HandleInternalError(response, request, err) return } - response.WriteEntity(workspace) + _ = response.WriteEntity(workspace) } func (h *handler) PatchWorkspaceTemplate(req *restful.Request, resp *restful.Response) { @@ -227,7 +222,7 @@ func (h *handler) PatchWorkspaceTemplate(req *restful.Request, resp *restful.Res return } - resp.WriteEntity(patched) + _ = resp.WriteEntity(patched) } func (h *handler) ListWorkspaceTemplates(req *restful.Request, resp *restful.Response) { @@ -248,5 +243,5 @@ func (h *handler) ListWorkspaceTemplates(req *restful.Request, resp *restful.Res return } - resp.WriteEntity(result) + _ = resp.WriteEntity(result) } From 68075ac3dc8772404618de93333bc6aa9d48c5e6 Mon Sep 17 00:00:00 2001 From: hongming Date: Wed, 25 Dec 2024 11:14:06 +0800 Subject: [PATCH 2/4] clean up unnecessary warning logs (#2140) Signed-off-by: hongming --- pkg/controller/core/installplan_controller.go | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/pkg/controller/core/installplan_controller.go b/pkg/controller/core/installplan_controller.go index b846cf8bd..33cdada1c 100644 --- a/pkg/controller/core/installplan_controller.go +++ b/pkg/controller/core/installplan_controller.go @@ -63,30 +63,24 @@ import ( ) const ( - installPlanController = "installplan" - installPlanProtection = "kubesphere.io/installplan-protection" - systemWorkspace = "system-workspace" - agentReleaseFormat = "%s-agent" - defaultRoleFormat = "kubesphere:%s:helm-executor" - defaultRoleBindingFormat = defaultRoleFormat - defaultClusterRoleFormat = "kubesphere:%s:helm-executor" - permissionDefinitionFile = "permissions.yaml" - defaultClusterRoleBindingFormat = defaultClusterRoleFormat - tagAgent = "agent" - tagExtension = "extension" - - upgradeSuccessful = "UpgradeSuccessful" - upgradeFailed = "UpgradeFailed" - installSuccessful = "InstallSuccessful" - installFailed = "InstallFailed" - initialized = "Initialized" - uninstallSuccessful = "UninstallSuccessful" - uninstallFailed = "UninstallFailed" - relatedResourceNotReady = "RelatedResourceNotReady" - relatedResourceReady = "RelatedResourceReady" - - typeHelmRelease = "helm.sh/release.v1" - + installPlanController = "installplan" + installPlanProtection = "kubesphere.io/installplan-protection" + systemWorkspace = "system-workspace" + agentReleaseFormat = "%s-agent" + defaultRoleFormat = "kubesphere:%s:helm-executor" + defaultRoleBindingFormat = defaultRoleFormat + defaultClusterRoleFormat = "kubesphere:%s:helm-executor" + permissionDefinitionFile = "permissions.yaml" + defaultClusterRoleBindingFormat = defaultClusterRoleFormat + tagAgent = "agent" + tagExtension = "extension" + upgradeSuccessful = "UpgradeSuccessful" + upgradeFailed = "UpgradeFailed" + installSuccessful = "InstallSuccessful" + installFailed = "InstallFailed" + initialized = "Initialized" + uninstallFailed = "UninstallFailed" + typeHelmRelease = "helm.sh/release.v1" globalExtensionIngressClassName = "global.extension.ingress.ingressClassName" globalExtensionIngressDomainSuffix = "global.extension.ingress.domainSuffix" globalExtensionIngressHTTPPort = "global.extension.ingress.httpPort" @@ -274,7 +268,7 @@ func (r *InstallPlanReconciler) Reconcile(ctx context.Context, req ctrl.Request) if !controllerutil.ContainsFinalizer(plan, installPlanProtection) { expected := plan.DeepCopy() controllerutil.AddFinalizer(expected, installPlanProtection) - return ctrl.Result{Requeue: true}, r.Patch(ctx, expected, client.MergeFrom(plan)) + return ctrl.Result{}, r.Patch(ctx, expected, client.MergeFrom(plan)) } targetNamespace := extensionVersion.Spec.Namespace @@ -284,7 +278,7 @@ func (r *InstallPlanReconciler) Reconcile(ctx context.Context, req ctrl.Request) if plan.Status.TargetNamespace != targetNamespace { plan.Status.TargetNamespace = targetNamespace - return ctrl.Result{Requeue: true}, r.updateInstallPlan(ctx, plan) + return ctrl.Result{}, r.updateInstallPlan(ctx, plan) } if err := r.syncInstallPlanStatus(ctx, plan); err != nil { From 3d40b1905defdd2c7be04677f06df45e6c36e490 Mon Sep 17 00:00:00 2001 From: hongming Date: Thu, 26 Dec 2024 15:45:34 +0800 Subject: [PATCH 3/4] fix: the problem of conflicting controller output logs caused by duplicate initialization of roles and workspace roles when the controller is started (#2139) Signed-off-by: peng wu <2030047311@qq.com> Signed-off-by: hongming --- pkg/controller/namespace/namespace_controller.go | 9 ++++----- pkg/controller/role/role_controller.go | 16 ++++++---------- .../workspacetemplate_controller.go | 12 ++++++------ 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/pkg/controller/namespace/namespace_controller.go b/pkg/controller/namespace/namespace_controller.go index 4fba446d9..ad2549fda 100644 --- a/pkg/controller/namespace/namespace_controller.go +++ b/pkg/controller/namespace/namespace_controller.go @@ -89,6 +89,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // The object is not being deleted, so if it does not have our finalizer, // then lets add the finalizer and update the object. if workspaceLabelExists && !controllerutil.ContainsFinalizer(namespace, constants.CascadingDeletionFinalizer) { + if err := r.initRoles(ctx, namespace); err != nil { + return ctrl.Result{}, errors.Wrapf(err, "failed to init roles in namespace %s", namespace.Name) + } if err := r.initCreatorRoleBinding(ctx, namespace); err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to init creator role binding") } @@ -104,11 +107,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return ctrl.Result{}, nil } - if workspaceLabelExists { - if err := r.initRoles(ctx, namespace); err != nil { - return ctrl.Result{}, errors.Wrapf(err, "failed to init roles in namespace %s", namespace.Name) - } - } else { + if !workspaceLabelExists { if err := r.cleanUp(ctx, namespace); err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to clean up namespace %s", namespace.Name) } diff --git a/pkg/controller/role/role_controller.go b/pkg/controller/role/role_controller.go index 4e60573fd..0f1eb3164 100644 --- a/pkg/controller/role/role_controller.go +++ b/pkg/controller/role/role_controller.go @@ -9,23 +9,18 @@ import ( "context" "fmt" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - rbacutils "kubesphere.io/kubesphere/pkg/utils/rbac" - - rbacv1 "k8s.io/api/rbac/v1" - - kscontroller "kubesphere.io/kubesphere/pkg/controller" - "github.com/go-logr/logr" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/record" iamv1beta1 "kubesphere.io/api/iam/v1beta1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" rbachelper "kubesphere.io/kubesphere/pkg/componenthelper/auth/rbac" + kscontroller "kubesphere.io/kubesphere/pkg/controller" + rbacutils "kubesphere.io/kubesphere/pkg/utils/rbac" ) const ( @@ -97,6 +92,7 @@ func (r *Reconciler) syncToKubernetes(ctx context.Context, role *iamv1beta1.Role if err != nil { r.logger.Error(err, "sync role failed", "namespace", role.Namespace, "role", role.Name) + return err } r.logger.V(4).Info("sync role to K8s", "namespace", role.Namespace, "role", role.Name, "op", op) diff --git a/pkg/controller/workspacetemplate/workspacetemplate_controller.go b/pkg/controller/workspacetemplate/workspacetemplate_controller.go index 36232cb4a..03c5578d0 100644 --- a/pkg/controller/workspacetemplate/workspacetemplate_controller.go +++ b/pkg/controller/workspacetemplate/workspacetemplate_controller.go @@ -115,6 +115,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu // The object is not being deleted, so if it does not have our finalizer, // then lets add the finalizer and update the object. if !controllerutil.ContainsFinalizer(workspaceTemplate, constants.CascadingDeletionFinalizer) { + if err := r.initWorkspaceRoles(ctx, workspaceTemplate); err != nil { + return ctrl.Result{}, err + } + if err := r.initManagerRoleBinding(ctx, workspaceTemplate); err != nil { + return ctrl.Result{}, err + } updated := workspaceTemplate.DeepCopy() // Remove legacy finalizer controllerutil.RemoveFinalizer(updated, "finalizers.workspacetemplate.kubesphere.io") @@ -140,12 +146,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return ctrl.Result{}, nil } - if err := r.initWorkspaceRoles(ctx, workspaceTemplate); err != nil { - return ctrl.Result{}, err - } - if err := r.initManagerRoleBinding(ctx, workspaceTemplate); err != nil { - return ctrl.Result{}, err - } if err := r.multiClusterSync(ctx, workspaceTemplate); err != nil { return ctrl.Result{}, err } From bc128dcf784206ed74517324e78282531ce3117f Mon Sep 17 00:00:00 2001 From: smartcat999 <49057502+smartcat999@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:48:08 +0800 Subject: [PATCH 4/4] Fix workspacerole sync condition (#2142) * fix: Fixed the issue that role and rolebinding do not trigger synchronization when binding a workspace to a cluster Signed-off-by: peng wu <2030047311@qq.com> * fix: update goimports Signed-off-by: peng wu <2030047311@qq.com> * fix: update workspace sync condition && update list options Signed-off-by: peng wu <2030047311@qq.com> * fix: rename enqueue request map func for workspacerole and workspacerolebinding Signed-off-by: peng wu <2030047311@qq.com> * fix: workspace role sync logic Signed-off-by: peng wu <2030047311@qq.com> --------- Signed-off-by: peng wu <2030047311@qq.com> Signed-off-by: hongming --- .../workspacerole/workspacerole_controller.go | 32 +++++++++-- .../workspacerolebinding_controller.go | 55 +++++++++++++++++-- .../workspacetemplate/predicate/predicate.go | 46 ++++++++++++++++ 3 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 pkg/controller/workspacetemplate/predicate/predicate.go diff --git a/pkg/controller/workspacerole/workspacerole_controller.go b/pkg/controller/workspacerole/workspacerole_controller.go index 0b364694d..15fc549e0 100644 --- a/pkg/controller/workspacerole/workspacerole_controller.go +++ b/pkg/controller/workspacerole/workspacerole_controller.go @@ -8,7 +8,6 @@ package workspacerole import ( "context" "fmt" - "strings" "github.com/go-logr/logr" @@ -19,9 +18,6 @@ import ( "k8s.io/client-go/tools/record" "k8s.io/client-go/util/retry" "k8s.io/klog/v2" - clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1" - iamv1beta1 "kubesphere.io/api/iam/v1beta1" - tenantv1beta1 "kubesphere.io/api/tenant/v1beta1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -30,11 +26,16 @@ import ( "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" + clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1" + iamv1beta1 "kubesphere.io/api/iam/v1beta1" + tenantv1beta1 "kubesphere.io/api/tenant/v1beta1" + rbachelper "kubesphere.io/kubesphere/pkg/componenthelper/auth/rbac" "kubesphere.io/kubesphere/pkg/constants" kscontroller "kubesphere.io/kubesphere/pkg/controller" "kubesphere.io/kubesphere/pkg/controller/cluster/predicate" clusterutils "kubesphere.io/kubesphere/pkg/controller/cluster/utils" + workspacetemplatepredicate "kubesphere.io/kubesphere/pkg/controller/workspacetemplate/predicate" "kubesphere.io/kubesphere/pkg/controller/workspacetemplate/utils" "kubesphere.io/kubesphere/pkg/utils/clusterclient" "kubesphere.io/kubesphere/pkg/utils/k8sutil" @@ -77,13 +78,16 @@ func (r *Reconciler) SetupWithManager(mgr *kscontroller.Manager) error { For(&iamv1beta1.WorkspaceRole{}). Watches( &clusterv1alpha1.Cluster{}, - handler.EnqueueRequestsFromMapFunc(r.mapper), + handler.EnqueueRequestsFromMapFunc(r.clusterSync), builder.WithPredicates(predicate.ClusterStatusChangedPredicate{}), ). + Watches(&tenantv1beta1.WorkspaceTemplate{}, + handler.EnqueueRequestsFromMapFunc(r.workspaceSync), + builder.WithPredicates(workspacetemplatepredicate.WorkspaceStatusChangedPredicate{})). Complete(r) } -func (r *Reconciler) mapper(ctx context.Context, o client.Object) []reconcile.Request { +func (r *Reconciler) clusterSync(ctx context.Context, o client.Object) []reconcile.Request { cluster := o.(*clusterv1alpha1.Cluster) if !clusterutils.IsClusterReady(cluster) { return []reconcile.Request{} @@ -108,6 +112,22 @@ func (r *Reconciler) mapper(ctx context.Context, o client.Object) []reconcile.Re return result } +func (r *Reconciler) workspaceSync(ctx context.Context, object client.Object) []reconcile.Request { + workspaceTemplate := object.(*tenantv1beta1.WorkspaceTemplate) + workspaceRoles := &iamv1beta1.WorkspaceRoleList{} + if err := r.List(ctx, workspaceRoles, client.MatchingLabels{ + tenantv1beta1.WorkspaceLabel: workspaceTemplate.Name, + }); err != nil { + r.logger.Error(err, "failed to list workspace roles") + return []reconcile.Request{} + } + var result []reconcile.Request + for _, workspaceRole := range workspaceRoles.Items { + result = append(result, reconcile.Request{NamespacedName: types.NamespacedName{Name: workspaceRole.Name}}) + } + return result +} + // +kubebuilder:rbac:groups=iam.kubesphere.io,resources=workspaceroles,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=tenant.kubesphere.io,resources=workspaces,verbs=get;list;watch; diff --git a/pkg/controller/workspacerolebinding/workspacerolebinding_controller.go b/pkg/controller/workspacerolebinding/workspacerolebinding_controller.go index 4a83ee360..54d618e7d 100644 --- a/pkg/controller/workspacerolebinding/workspacerolebinding_controller.go +++ b/pkg/controller/workspacerolebinding/workspacerolebinding_controller.go @@ -12,8 +12,6 @@ import ( "sort" "strings" - kscontroller "kubesphere.io/kubesphere/pkg/controller" - "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/rbac/v1" @@ -24,18 +22,24 @@ import ( toolscache "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" - clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1" - iamv1beta1 "kubesphere.io/api/iam/v1beta1" - tenantv1beta1 "kubesphere.io/api/tenant/v1beta1" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" + clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1" + iamv1beta1 "kubesphere.io/api/iam/v1beta1" + tenantv1beta1 "kubesphere.io/api/tenant/v1beta1" + "kubesphere.io/kubesphere/pkg/constants" + kscontroller "kubesphere.io/kubesphere/pkg/controller" + "kubesphere.io/kubesphere/pkg/controller/cluster/predicate" clusterutils "kubesphere.io/kubesphere/pkg/controller/cluster/utils" + workspacetemplatepredicate "kubesphere.io/kubesphere/pkg/controller/workspacetemplate/predicate" "kubesphere.io/kubesphere/pkg/controller/workspacetemplate/utils" "kubesphere.io/kubesphere/pkg/utils/clusterclient" "kubesphere.io/kubesphere/pkg/utils/k8sutil" @@ -117,9 +121,50 @@ func (r *Reconciler) SetupWithManager(mgr *kscontroller.Manager) error { Named(controllerName). WithOptions(controller.Options{MaxConcurrentReconciles: 2}). For(&iamv1beta1.WorkspaceRoleBinding{}). + Watches( + &clusterv1alpha1.Cluster{}, + handler.EnqueueRequestsFromMapFunc(r.clusterSync), + builder.WithPredicates(predicate.ClusterStatusChangedPredicate{}), + ). + Watches(&tenantv1beta1.WorkspaceTemplate{}, + handler.EnqueueRequestsFromMapFunc(r.workspaceSync), + builder.WithPredicates(workspacetemplatepredicate.WorkspaceStatusChangedPredicate{})). Complete(r) } +func (r *Reconciler) clusterSync(ctx context.Context, object client.Object) []reconcile.Request { + cluster := object.(*clusterv1alpha1.Cluster) + if !clusterutils.IsClusterReady(cluster) { + return []reconcile.Request{} + } + workspaceRoleBindings := &iamv1beta1.WorkspaceRoleBindingList{} + if err := r.List(ctx, workspaceRoleBindings); err != nil { + r.logger.Error(err, "failed to list workspace roles") + return []reconcile.Request{} + } + var result []reconcile.Request + for _, workspaceRoleBinding := range workspaceRoleBindings.Items { + result = append(result, reconcile.Request{NamespacedName: types.NamespacedName{Name: workspaceRoleBinding.Name}}) + } + return result +} + +func (r *Reconciler) workspaceSync(ctx context.Context, object client.Object) []reconcile.Request { + workspaceTemplate := object.(*tenantv1beta1.WorkspaceTemplate) + workspaceRoleBindings := &iamv1beta1.WorkspaceRoleBindingList{} + if err := r.List(ctx, workspaceRoleBindings, client.MatchingLabels{ + tenantv1beta1.WorkspaceLabel: workspaceTemplate.Name, + }); err != nil { + r.logger.Error(err, "failed to list workspace roles") + return []reconcile.Request{} + } + var result []reconcile.Request + for _, workspaceRoleBinding := range workspaceRoleBindings.Items { + result = append(result, reconcile.Request{NamespacedName: types.NamespacedName{Name: workspaceRoleBinding.Name}}) + } + return result +} + // +kubebuilder:rbac:groups=iam.kubesphere.io,resources=workspacerolebindings,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=types.kubefed.io,resources=federatedworkspacerolebindings,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=tenant.kubesphere.io,resources=workspaces,verbs=get;list;watch; diff --git a/pkg/controller/workspacetemplate/predicate/predicate.go b/pkg/controller/workspacetemplate/predicate/predicate.go new file mode 100644 index 000000000..87bbe6266 --- /dev/null +++ b/pkg/controller/workspacetemplate/predicate/predicate.go @@ -0,0 +1,46 @@ +/* + * Please refer to the LICENSE file in the root directory of the project. + * https://github.com/kubesphere/kubesphere/blob/master/LICENSE + */ + +package predicate + +import ( + "reflect" + + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + tenantv1alpha1 "kubesphere.io/api/tenant/v1beta1" +) + +type WorkspaceStatusChangedPredicate struct { + predicate.Funcs +} + +func (WorkspaceStatusChangedPredicate) Update(e event.UpdateEvent) bool { + oldWorkspaceTemplate, ok := e.ObjectOld.(*tenantv1alpha1.WorkspaceTemplate) + if !ok { + return false + } + newWorkspaceTemplate, ok := e.ObjectNew.(*tenantv1alpha1.WorkspaceTemplate) + if !ok { + return false + } + if !reflect.DeepEqual(oldWorkspaceTemplate.Spec, newWorkspaceTemplate.Spec) { + return true + } + return false +} + +func (WorkspaceStatusChangedPredicate) Create(_ event.CreateEvent) bool { + return false +} + +func (WorkspaceStatusChangedPredicate) Delete(_ event.DeleteEvent) bool { + return false +} + +func (WorkspaceStatusChangedPredicate) Generic(_ event.GenericEvent) bool { + return false +}