package clustersetbinding import ( "encoding/json" "net/http" "reflect" "testing" clusterv1alpha1 "github.com/open-cluster-management/api/cluster/v1alpha1" admissionv1beta1 "k8s.io/api/admission/v1beta1" authorizationv1 "k8s.io/api/authorization/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" kubefake "k8s.io/client-go/kubernetes/fake" clienttesting "k8s.io/client-go/testing" ) var managedclustersetbindingSchema = metav1.GroupVersionResource{ Group: "cluster.open-cluster-management.io", Version: "v1alpha1", Resource: "managedclustersetbindings", } func TestManagedClusterValidate(t *testing.T) { cases := []struct { name string request *admissionv1beta1.AdmissionRequest expectedResponse *admissionv1beta1.AdmissionResponse allowBindingToClusterSet bool }{ { name: "validate non-managedclustersetbindings request", request: &admissionv1beta1.AdmissionRequest{ Resource: metav1.GroupVersionResource{ Group: "test.open-cluster-management.io", Version: "v1", Resource: "tests", }, }, expectedResponse: &admissionv1beta1.AdmissionResponse{ Allowed: true, }, }, { name: "validate deleting operation", request: &admissionv1beta1.AdmissionRequest{ Resource: managedclustersetbindingSchema, Operation: admissionv1beta1.Delete, }, expectedResponse: &admissionv1beta1.AdmissionResponse{ Allowed: true, }, }, { name: "validate creating cluster set binding", request: &admissionv1beta1.AdmissionRequest{ Resource: managedclustersetbindingSchema, Operation: admissionv1beta1.Create, Object: newManagedClusterSetBindingObj("ns1", "cs1", "cs1", nil), }, allowBindingToClusterSet: true, expectedResponse: &admissionv1beta1.AdmissionResponse{ Allowed: true, }, }, { name: "validate creating cluster set binding with unmatched name", request: &admissionv1beta1.AdmissionRequest{ Resource: managedclustersetbindingSchema, Operation: admissionv1beta1.Create, Object: newManagedClusterSetBindingObj("ns1", "csb1", "cs1", nil), }, expectedResponse: &admissionv1beta1.AdmissionResponse{ Allowed: false, Result: &metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusBadRequest, Reason: metav1.StatusReasonBadRequest, Message: "The ManagedClusterSetBinding must have the same name as the target ManagedClusterSet", }, }, }, { name: "validate creating cluster set binding without permission", request: &admissionv1beta1.AdmissionRequest{ Resource: managedclustersetbindingSchema, Operation: admissionv1beta1.Create, Object: newManagedClusterSetBindingObj("ns1", "cs1", "cs1", nil), }, expectedResponse: &admissionv1beta1.AdmissionResponse{ Allowed: false, Result: &metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusForbidden, Reason: metav1.StatusReasonForbidden, Message: "user \"\" is not allowed to bind cluster set \"cs1\"", }, }, }, { name: "validate updating cluster set binding", request: &admissionv1beta1.AdmissionRequest{ Resource: managedclustersetbindingSchema, Operation: admissionv1beta1.Update, Object: newManagedClusterSetBindingObj("ns1", "cs1", "cs1", nil), OldObject: newManagedClusterSetBindingObj("ns1", "cs1", "cs2", map[string]string{ "team": "team1", }), }, allowBindingToClusterSet: true, expectedResponse: &admissionv1beta1.AdmissionResponse{ Allowed: true, }, }, { name: "validate updating cluster set binding with different cluster set", request: &admissionv1beta1.AdmissionRequest{ Resource: managedclustersetbindingSchema, Operation: admissionv1beta1.Update, Object: newManagedClusterSetBindingObj("ns1", "cs1", "cs2", nil), OldObject: newManagedClusterSetBindingObj("ns1", "cs1", "cs1", nil), }, expectedResponse: &admissionv1beta1.AdmissionResponse{ Allowed: false, Result: &metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusBadRequest, Reason: metav1.StatusReasonBadRequest, Message: "The ManagedClusterSetBinding must have the same name as the target ManagedClusterSet", }, }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { kubeClient := kubefake.NewSimpleClientset() kubeClient.PrependReactor( "create", "subjectaccessreviews", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { return true, &authorizationv1.SubjectAccessReview{ Status: authorizationv1.SubjectAccessReviewStatus{ Allowed: c.allowBindingToClusterSet, }, }, nil }, ) admissionHook := &ManagedClusterSetBindingValidatingAdmissionHook{ kubeClient: kubeClient, } actualResponse := admissionHook.Validate(c.request) if !reflect.DeepEqual(actualResponse, c.expectedResponse) { t.Errorf("expected %#v but got: %#v", c.expectedResponse.Result, actualResponse.Result) } }) } } func newManagedClusterSetBindingObj(namespace, name, clusterSetName string, labels map[string]string) runtime.RawExtension { managedClusterSetBinding := &clusterv1alpha1.ManagedClusterSetBinding{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, Name: name, Labels: labels, }, Spec: clusterv1alpha1.ManagedClusterSetBindingSpec{ ClusterSet: clusterSetName, }, } bindingObj, _ := json.Marshal(managedClusterSetBinding) return runtime.RawExtension{ Raw: bindingObj, } }