feat: kubesphere 4.0 (#6115)

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

---------

Signed-off-by: ci-bot <ci-bot@kubesphere.io>
Co-authored-by: ks-ci-bot <ks-ci-bot@example.com>
Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
KubeSphere CI Bot
2024-09-06 11:05:52 +08:00
committed by GitHub
parent b5015ec7b9
commit 447a51f08b
8557 changed files with 546695 additions and 1146174 deletions

View File

@@ -1,189 +1,103 @@
/*
Copyright 2020 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
* Please refer to the LICENSE file in the root directory of the project.
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
*/
package groupbinding
import (
"context"
"fmt"
"reflect"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/predicate"
kscontroller "kubesphere.io/kubesphere/pkg/controller"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog/v2"
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"
iamv1alpha2 "kubesphere.io/api/iam/v1alpha2"
fedv1beta1types "kubesphere.io/api/types/v1beta1"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
iamv1alpha2informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2"
fedv1beta1informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/types/v1beta1"
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
fedv1beta1lister "kubesphere.io/kubesphere/pkg/client/listers/types/v1beta1"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/controller/utils/controller"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
)
const (
successSynced = "Synced"
messageResourceSynced = "GroupBinding synced successfully"
controllerName = "groupbinding-controller"
finalizer = "finalizers.kubesphere.io/groupsbindings"
controllerName = "groupbinding"
finalizer = "finalizers.kubesphere.io/groupsbindings"
)
type Controller struct {
controller.BaseController
k8sClient kubernetes.Interface
ksClient kubesphere.Interface
groupBindingLister iamv1alpha2listers.GroupBindingLister
recorder record.EventRecorder
federatedGroupBindingLister fedv1beta1lister.FederatedGroupBindingLister
multiClusterEnabled bool
type Reconciler struct {
client.Client
recorder record.EventRecorder
}
// NewController creates GroupBinding Controller instance
func NewController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface,
groupBindingInformer iamv1alpha2informers.GroupBindingInformer,
federatedGroupBindingInformer fedv1beta1informers.FederatedGroupBindingInformer, multiClusterEnabled bool) *Controller {
klog.V(4).Info("Creating event broadcaster")
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(klog.Infof)
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: k8sClient.CoreV1().Events("")})
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerName})
ctl := &Controller{
BaseController: controller.BaseController{
Workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "GroupBinding"),
Synced: []cache.InformerSynced{groupBindingInformer.Informer().HasSynced},
Name: controllerName,
},
k8sClient: k8sClient,
ksClient: ksClient,
groupBindingLister: groupBindingInformer.Lister(),
multiClusterEnabled: multiClusterEnabled,
recorder: recorder,
}
ctl.Handler = ctl.reconcile
if ctl.multiClusterEnabled {
ctl.federatedGroupBindingLister = federatedGroupBindingInformer.Lister()
ctl.Synced = append(ctl.Synced, federatedGroupBindingInformer.Informer().HasSynced)
}
klog.Info("Setting up event handlers")
groupBindingInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: ctl.Enqueue,
UpdateFunc: func(old, new interface{}) {
ctl.Enqueue(new)
},
DeleteFunc: ctl.Enqueue,
})
return ctl
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
r.recorder = mgr.GetEventRecorderFor(controllerName)
r.Client = mgr.GetClient()
return builder.
ControllerManagedBy(mgr).
For(
&iamv1beta1.GroupBinding{},
builder.WithPredicates(
predicate.ResourceVersionChangedPredicate{},
),
).
WithOptions(controller.Options{
MaxConcurrentReconciles: 2,
}).
Named(controllerName).
Complete(r)
}
// reconcile handles GroupBinding informer events, it updates user's Groups property with the current GroupBinding.
func (c *Controller) reconcile(key string) error {
groupBinding, err := c.groupBindingLister.Get(key)
if err != nil {
if errors.IsNotFound(err) {
utilruntime.HandleError(fmt.Errorf("groupbinding '%s' in work queue no longer exists", key))
return nil
}
klog.Error(err)
return err
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
groupBinding := &iamv1beta1.GroupBinding{}
if err := r.Get(ctx, req.NamespacedName, groupBinding); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if groupBinding.ObjectMeta.DeletionTimestamp.IsZero() {
var g *iamv1alpha2.GroupBinding
var g *iamv1beta1.GroupBinding
if !sliceutil.HasString(groupBinding.Finalizers, finalizer) {
g = groupBinding.DeepCopy()
g.ObjectMeta.Finalizers = append(g.ObjectMeta.Finalizers, finalizer)
}
if c.multiClusterEnabled {
// Ensure not controlled by Kubefed
if groupBinding.Labels == nil || groupBinding.Labels[constants.KubefedManagedLabel] != "false" {
if g == nil {
g = groupBinding.DeepCopy()
}
if g.Labels == nil {
g.Labels = make(map[string]string, 0)
}
g.Labels[constants.KubefedManagedLabel] = "false"
}
}
if g != nil {
if _, err = c.ksClient.IamV1alpha2().GroupBindings().Update(context.Background(), g, metav1.UpdateOptions{}); err != nil {
return err
}
// Skip reconcile when group is updated.
return nil
return ctrl.Result{}, r.Update(ctx, g)
}
} else {
// The object is being deleted
if sliceutil.HasString(groupBinding.ObjectMeta.Finalizers, finalizer) {
if err = c.unbindUser(groupBinding); err != nil {
klog.Error(err)
return err
if err := r.unbindUser(ctx, groupBinding); err != nil {
return ctrl.Result{}, err
}
groupBinding.Finalizers = sliceutil.RemoveString(groupBinding.ObjectMeta.Finalizers, func(item string) bool {
return item == finalizer
})
if _, err = c.ksClient.IamV1alpha2().GroupBindings().Update(context.Background(), groupBinding, metav1.UpdateOptions{}); err != nil {
return err
}
return ctrl.Result{}, r.Update(ctx, groupBinding)
}
return nil
return ctrl.Result{}, nil
}
if err = c.bindUser(groupBinding); err != nil {
klog.Error(err)
return err
if err := r.bindUser(ctx, groupBinding); err != nil {
return ctrl.Result{}, err
}
// synchronization through kubefed-controller when multi cluster is enabled
if c.multiClusterEnabled {
if err = c.multiClusterSync(groupBinding); err != nil {
klog.Error(err)
return err
}
}
// TODO: sync logic needs to be updated and no longer relies on KubeFed, it needs to be synchronized manually.
c.recorder.Event(groupBinding, corev1.EventTypeNormal, successSynced, messageResourceSynced)
return nil
r.recorder.Event(groupBinding, corev1.EventTypeNormal, kscontroller.Synced, kscontroller.MessageResourceSynced)
return ctrl.Result{}, nil
}
func (c *Controller) Start(ctx context.Context) error {
return c.Run(2, ctx.Done())
}
func (c *Controller) unbindUser(groupBinding *iamv1alpha2.GroupBinding) error {
return c.updateUserGroups(groupBinding, func(groups []string, group string) (bool, []string) {
func (r *Reconciler) unbindUser(ctx context.Context, groupBinding *iamv1beta1.GroupBinding) error {
return r.updateUserGroups(ctx, groupBinding, func(groups []string, group string) (bool, []string) {
// remove a group from the groups
if sliceutil.HasString(groups, group) {
groups := sliceutil.RemoveString(groups, func(item string) bool {
@@ -195,9 +109,9 @@ func (c *Controller) unbindUser(groupBinding *iamv1alpha2.GroupBinding) error {
})
}
func (c *Controller) bindUser(groupBinding *iamv1alpha2.GroupBinding) error {
return c.updateUserGroups(groupBinding, func(groups []string, group string) (bool, []string) {
// add group to the groups
func (r *Reconciler) bindUser(ctx context.Context, groupBinding *iamv1beta1.GroupBinding) error {
return r.updateUserGroups(ctx, groupBinding, func(groups []string, group string) (bool, []string) {
// add a group to the groups
if !sliceutil.HasString(groups, group) {
groups := append(groups, group)
return true, groups
@@ -206,104 +120,40 @@ func (c *Controller) bindUser(groupBinding *iamv1alpha2.GroupBinding) error {
})
}
// Udpate user's Group property. So no need to query user's groups when authorizing.
func (c *Controller) updateUserGroups(groupBinding *iamv1alpha2.GroupBinding, operator func(groups []string, group string) (bool, []string)) error {
// Update user's Group property. So no need to query user's groups when authorizing.
func (r *Reconciler) updateUserGroups(ctx context.Context, groupBinding *iamv1beta1.GroupBinding, operator func(groups []string, group string) (bool, []string)) error {
for _, u := range groupBinding.Users {
// Ignore the user if the user if being deleted.
if user, err := c.ksClient.IamV1alpha2().Users().Get(context.Background(), u, metav1.GetOptions{}); err == nil && user.ObjectMeta.DeletionTimestamp.IsZero() {
// Ignore the user if the user being deleted.
user := &iamv1beta1.User{}
if err := r.Get(ctx, client.ObjectKey{Name: u}, user); err != nil {
if errors.IsNotFound(err) {
klog.Infof("user %s doesn't exist any more", u)
continue
}
return err
}
if changed, groups := operator(user.Spec.Groups, groupBinding.GroupRef.Name); changed {
if !user.DeletionTimestamp.IsZero() {
continue
}
if err := c.patchUser(user, groups); err != nil {
if errors.IsNotFound(err) {
klog.Infof("user %s doesn't exist any more", u)
continue
}
klog.Error(err)
return err
if changed, groups := operator(user.Spec.Groups, groupBinding.GroupRef.Name); changed {
if err := r.patchUser(ctx, user, groups); err != nil {
if errors.IsNotFound(err) {
klog.Infof("user %s doesn't exist any more", u)
continue
}
klog.Error(err)
return err
}
}
}
return nil
}
func (c *Controller) patchUser(user *iamv1alpha2.User, groups []string) error {
func (r *Reconciler) patchUser(ctx context.Context, user *iamv1beta1.User, groups []string) error {
newUser := user.DeepCopy()
newUser.Spec.Groups = groups
patch := client.MergeFrom(user)
patchData, _ := patch.Data(newUser)
if _, err := c.ksClient.IamV1alpha2().Users().
Patch(context.Background(), user.Name, patch.Type(), patchData, metav1.PatchOptions{}); err != nil {
return err
}
return nil
}
func (c *Controller) multiClusterSync(groupBinding *iamv1alpha2.GroupBinding) error {
fedGroupBinding, err := c.federatedGroupBindingLister.Get(groupBinding.Name)
if err != nil {
if errors.IsNotFound(err) {
return c.createFederatedGroupBinding(groupBinding)
}
klog.Error(err)
return err
}
if !reflect.DeepEqual(fedGroupBinding.Spec.Template.GroupRef, groupBinding.GroupRef) ||
!reflect.DeepEqual(fedGroupBinding.Spec.Template.Users, groupBinding.Users) ||
!reflect.DeepEqual(fedGroupBinding.Spec.Template.Labels, groupBinding.Labels) {
fedGroupBinding.Spec.Template.GroupRef = groupBinding.GroupRef
fedGroupBinding.Spec.Template.Users = groupBinding.Users
fedGroupBinding.Spec.Template.Labels = groupBinding.Labels
if _, err = c.ksClient.TypesV1beta1().FederatedGroupBindings().Update(context.Background(), fedGroupBinding, metav1.UpdateOptions{}); err != nil {
return err
}
}
return nil
}
func (c *Controller) createFederatedGroupBinding(groupBinding *iamv1alpha2.GroupBinding) error {
federatedGroup := &fedv1beta1types.FederatedGroupBinding{
TypeMeta: metav1.TypeMeta{
Kind: fedv1beta1types.FederatedGroupBindingKind,
APIVersion: fedv1beta1types.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: groupBinding.Name,
},
Spec: fedv1beta1types.FederatedGroupBindingSpec{
Template: fedv1beta1types.GroupBindingTemplate{
ObjectMeta: metav1.ObjectMeta{
Labels: groupBinding.Labels,
},
GroupRef: groupBinding.GroupRef,
Users: groupBinding.Users,
},
Placement: fedv1beta1types.GenericPlacementFields{
ClusterSelector: &metav1.LabelSelector{},
},
},
}
// must bind groupBinding lifecycle
err := controllerutil.SetControllerReference(groupBinding, federatedGroup, scheme.Scheme)
if err != nil {
return err
}
if _, err = c.ksClient.TypesV1beta1().FederatedGroupBindings().Create(context.Background(), federatedGroup, metav1.CreateOptions{}); err != nil {
return err
}
return nil
return r.Patch(ctx, newUser, patch)
}

View File

@@ -1,385 +0,0 @@
/*
Copyright 2020 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package groupbinding
import (
"fmt"
"reflect"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
kubeinformers "k8s.io/client-go/informers"
k8sfake "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/kubernetes/scheme"
core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
iamv1alpha2 "kubesphere.io/api/iam/v1alpha2"
v1alpha2 "kubesphere.io/api/iam/v1alpha2"
fedv1beta1types "kubesphere.io/api/types/v1beta1"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
ksinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/constants"
)
var (
alwaysReady = func() bool { return true }
noResyncPeriodFunc = func() time.Duration { return 0 }
)
func init() {
v1alpha2.AddToScheme(scheme.Scheme)
}
type fixture struct {
t *testing.T
ksclient *fake.Clientset
k8sclient *k8sfake.Clientset
// Objects to put in the store.
groupBindingLister []*v1alpha2.GroupBinding
//nolint:unused
fedgroupBindingLister []*fedv1beta1types.FederatedGroupBinding
userLister []*v1alpha2.User
// Actions expected to happen on the client.
k8sactions []core.Action
ksactions []core.Action
// Objects from here preloaded into NewSimpleFake.
kubeobjects []runtime.Object
objects []runtime.Object
}
func newFixture(t *testing.T) *fixture {
f := &fixture{}
f.t = t
f.objects = []runtime.Object{}
f.kubeobjects = []runtime.Object{}
return f
}
func newGroupBinding(name string, users []string) *v1alpha2.GroupBinding {
return &v1alpha2.GroupBinding{
TypeMeta: metav1.TypeMeta{APIVersion: v1alpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-binding", name),
},
GroupRef: v1alpha2.GroupRef{
Name: name,
},
Users: users,
}
}
func newUser(name string) *v1alpha2.User {
return &v1alpha2.User{
TypeMeta: metav1.TypeMeta{APIVersion: v1alpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1alpha2.UserSpec{
Email: fmt.Sprintf("%s@kubesphere.io", name),
Lang: "zh-CN",
Description: "fake user",
},
}
}
func newFederatedGroupBinding(groupBinding *iamv1alpha2.GroupBinding) *fedv1beta1types.FederatedGroupBinding {
return &fedv1beta1types.FederatedGroupBinding{
TypeMeta: metav1.TypeMeta{
Kind: fedv1beta1types.FederatedGroupBindingKind,
APIVersion: fedv1beta1types.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: groupBinding.Name,
},
Spec: fedv1beta1types.FederatedGroupBindingSpec{
Template: fedv1beta1types.GroupBindingTemplate{
ObjectMeta: metav1.ObjectMeta{
Labels: groupBinding.Labels,
},
GroupRef: groupBinding.GroupRef,
Users: groupBinding.Users,
},
Placement: fedv1beta1types.GenericPlacementFields{
ClusterSelector: &metav1.LabelSelector{},
},
},
}
}
func (f *fixture) newController() (*Controller, ksinformers.SharedInformerFactory, kubeinformers.SharedInformerFactory) {
f.ksclient = fake.NewSimpleClientset(f.objects...)
f.k8sclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
ksinformers := ksinformers.NewSharedInformerFactory(f.ksclient, noResyncPeriodFunc())
k8sinformers := kubeinformers.NewSharedInformerFactory(f.k8sclient, noResyncPeriodFunc())
for _, groupBinding := range f.groupBindingLister {
err := ksinformers.Iam().V1alpha2().GroupBindings().Informer().GetIndexer().Add(groupBinding)
if err != nil {
f.t.Errorf("add groupBinding:%s", err)
}
}
for _, u := range f.userLister {
err := ksinformers.Iam().V1alpha2().Users().Informer().GetIndexer().Add(u)
if err != nil {
f.t.Errorf("add groupBinding:%s", err)
}
}
c := NewController(f.k8sclient, f.ksclient,
ksinformers.Iam().V1alpha2().GroupBindings(),
ksinformers.Types().V1beta1().FederatedGroupBindings(), true)
c.Synced = []cache.InformerSynced{alwaysReady}
c.recorder = &record.FakeRecorder{}
return c, ksinformers, k8sinformers
}
func (f *fixture) run(userName string) {
f.runController(userName, true, false)
}
//nolint:unused
func (f *fixture) runExpectError(userName string) {
f.runController(userName, true, true)
}
func (f *fixture) runController(groupBinding string, startInformers bool, expectError bool) {
c, i, k8sI := f.newController()
if startInformers {
stopCh := make(chan struct{})
defer close(stopCh)
i.Start(stopCh)
k8sI.Start(stopCh)
}
err := c.reconcile(groupBinding)
if !expectError && err != nil {
f.t.Errorf("error syncing groupBinding: %v", err)
} else if expectError && err == nil {
f.t.Error("expected error syncing groupBinding, got nil")
}
actions := filterInformerActions(f.ksclient.Actions())
for i, action := range actions {
if len(f.ksactions) < i+1 {
f.t.Errorf("%d unexpected actions: %+v", len(actions)-len(f.ksactions), actions[i:])
break
}
expectedAction := f.ksactions[i]
checkAction(expectedAction, action, f.t)
}
if len(f.ksactions) > len(actions) {
f.t.Errorf("%d additional expected actions:%+v", len(f.ksactions)-len(actions), f.ksactions[len(actions):])
}
k8sActions := filterInformerActions(f.k8sclient.Actions())
for i, action := range k8sActions {
if len(f.k8sactions) < i+1 {
f.t.Errorf("%d unexpected actions: %+v", len(k8sActions)-len(f.k8sactions), k8sActions[i:])
break
}
expectedAction := f.k8sactions[i]
checkAction(expectedAction, action, f.t)
}
if len(f.k8sactions) > len(k8sActions) {
f.t.Errorf("%d additional expected actions:%+v", len(f.k8sactions)-len(k8sActions), f.k8sactions[len(k8sActions):])
}
}
// checkAction verifies that expected and actual actions are equal and both have
// same attached resources
func checkAction(expected, actual core.Action, t *testing.T) {
if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) {
t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual)
return
}
if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual)
return
}
switch a := actual.(type) {
case core.CreateActionImpl:
e, _ := expected.(core.CreateActionImpl)
expObject := e.GetObject()
object := a.GetObject()
if !reflect.DeepEqual(expObject, object) {
t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
}
case core.UpdateActionImpl:
e, _ := expected.(core.UpdateActionImpl)
expObject := e.GetObject()
object := a.GetObject()
expUser := expObject.(*v1alpha2.GroupBinding)
groupBinding := object.(*v1alpha2.GroupBinding)
if !reflect.DeepEqual(expUser, groupBinding) {
t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
}
case core.PatchActionImpl:
e, _ := expected.(core.PatchActionImpl)
expPatch := e.GetPatch()
patch := a.GetPatch()
if !reflect.DeepEqual(expPatch, patch) {
t.Errorf("Action %s %s has wrong patch\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expPatch, patch))
}
default:
t.Errorf("Uncaptured Action %s %s, you should explicitly add a case to capture it",
actual.GetVerb(), actual.GetResource().Resource)
}
}
// filterInformerActions filters list and watch actions for testing resources.
// Since list and watch don't change resource state we can filter it to lower
// nose level in our tests.
func filterInformerActions(actions []core.Action) []core.Action {
var ret []core.Action
for _, action := range actions {
// filter out read action
if action.GetVerb() == "watch" || action.GetVerb() == "list" || action.GetVerb() == "get" {
continue
}
ret = append(ret, action)
}
return ret
}
func (f *fixture) expectUpdateGroupsFinalizerAction(groupBinding *v1alpha2.GroupBinding) {
expect := groupBinding.DeepCopy()
expect.Finalizers = []string{"finalizers.kubesphere.io/groupsbindings"}
expect.Labels = map[string]string{constants.KubefedManagedLabel: "false"}
action := core.NewUpdateAction(schema.GroupVersionResource{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "groupbindings"}, "", expect)
f.ksactions = append(f.ksactions, action)
}
func (f *fixture) expectUpdateGroupsDeleteAction(groupBinding *v1alpha2.GroupBinding) {
expect := groupBinding.DeepCopy()
expect.Finalizers = []string{}
action := core.NewUpdateAction(schema.GroupVersionResource{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "groupbindings"}, "", expect)
f.ksactions = append(f.ksactions, action)
}
func (f *fixture) expectPatchUserAction(user *v1alpha2.User, groups []string) {
newUser := user.DeepCopy()
newUser.Spec.Groups = groups
patch := client.MergeFrom(user)
patchData, _ := patch.Data(newUser)
f.ksactions = append(f.ksactions, core.NewPatchAction(schema.GroupVersionResource{Group: "iam.kubesphere.io", Resource: "users", Version: "v1alpha2"}, user.Namespace, user.Name, patch.Type(), patchData))
}
func (f *fixture) expectCreateFederatedGroupBindingsAction(groupBinding *v1alpha2.GroupBinding) {
b := newFederatedGroupBinding(groupBinding)
_ = controllerutil.SetControllerReference(groupBinding, b, scheme.Scheme)
actionCreate := core.NewCreateAction(schema.GroupVersionResource{Group: "types.kubefed.io", Version: "v1beta1", Resource: "federatedgroupbindings"}, "", b)
f.ksactions = append(f.ksactions, actionCreate)
}
func getKey(groupBinding *v1alpha2.GroupBinding, t *testing.T) string {
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(groupBinding)
if err != nil {
t.Errorf("Unexpected error getting key for groupBinding %v: %v", groupBinding.Name, err)
return ""
}
return key
}
func TestCreatesGroupBinding(t *testing.T) {
f := newFixture(t)
users := []string{"user1"}
groupbinding := newGroupBinding("test", users)
groupbinding.ObjectMeta.Finalizers = append(groupbinding.ObjectMeta.Finalizers, finalizer)
groupbinding.Labels = map[string]string{constants.KubefedManagedLabel: "false"}
f.groupBindingLister = append(f.groupBindingLister, groupbinding)
f.objects = append(f.objects, groupbinding)
user := newUser("user1")
f.userLister = append(f.userLister, user)
f.objects = append(f.objects, user)
expectGroups := []string{"test"}
f.expectPatchUserAction(user, expectGroups)
f.expectCreateFederatedGroupBindingsAction(groupbinding)
f.run(getKey(groupbinding, t))
}
func TestDeletesGroupBinding(t *testing.T) {
f := newFixture(t)
users := []string{"user1"}
groupbinding := newGroupBinding("test", users)
deletedGroup := groupbinding.DeepCopy()
deletedGroup.Finalizers = append(groupbinding.ObjectMeta.Finalizers, finalizer)
now := metav1.Now()
deletedGroup.ObjectMeta.DeletionTimestamp = &now
f.groupBindingLister = append(f.groupBindingLister, deletedGroup)
f.objects = append(f.objects, deletedGroup)
user := newUser("user1")
user.Spec.Groups = []string{"test"}
f.userLister = append(f.userLister, user)
f.objects = append(f.objects, user)
f.expectPatchUserAction(user, nil)
f.expectUpdateGroupsDeleteAction(deletedGroup)
f.run(getKey(deletedGroup, t))
}
func TestDoNothing(t *testing.T) {
f := newFixture(t)
users := []string{"user1"}
groupBinding := newGroupBinding("test", users)
f.groupBindingLister = append(f.groupBindingLister, groupBinding)
f.objects = append(f.objects, groupBinding)
f.expectUpdateGroupsFinalizerAction(groupBinding)
f.run(getKey(groupBinding, t))
}