kubernetes admissionreview_test 源码
kubernetes admissionreview_test 代码
文件路径:/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview_test.go
/*
Copyright 2019 The Kubernetes 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 request
import (
"reflect"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
admissionv1 "k8s.io/api/admission/v1"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
appsv1 "k8s.io/api/apps/v1"
authenticationv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/webhook"
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
"k8s.io/apiserver/pkg/authentication/user"
utilpointer "k8s.io/utils/pointer"
)
func TestVerifyAdmissionResponse(t *testing.T) {
v1beta1JSONPatch := admissionv1beta1.PatchTypeJSONPatch
v1JSONPatch := admissionv1.PatchTypeJSONPatch
emptyv1beta1Patch := admissionv1beta1.PatchType("")
emptyv1Patch := admissionv1.PatchType("")
invalidv1beta1Patch := admissionv1beta1.PatchType("Foo")
invalidv1Patch := admissionv1.PatchType("Foo")
testcases := []struct {
name string
uid types.UID
mutating bool
review runtime.Object
expectAuditAnnotations map[string]string
expectAllowed bool
expectPatch []byte
expectPatchType admissionv1.PatchType
expectResult *metav1.Status
expectErr string
}{
// Allowed validating
{
name: "v1beta1 allowed validating",
uid: "123",
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{Allowed: true},
},
expectAllowed: true,
},
{
name: "v1 allowed validating",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{UID: "123", Allowed: true},
},
expectAllowed: true,
},
// Allowed mutating
{
name: "v1beta1 allowed mutating",
uid: "123",
mutating: true,
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{Allowed: true},
},
expectAllowed: true,
},
{
name: "v1 allowed mutating",
uid: "123",
mutating: true,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{UID: "123", Allowed: true},
},
expectAllowed: true,
},
// Audit annotations
{
name: "v1beta1 auditAnnotations",
uid: "123",
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: true,
AuditAnnotations: map[string]string{"foo": "bar"},
},
},
expectAllowed: true,
expectAuditAnnotations: map[string]string{"foo": "bar"},
},
{
name: "v1 auditAnnotations",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
AuditAnnotations: map[string]string{"foo": "bar"},
},
},
expectAllowed: true,
expectAuditAnnotations: map[string]string{"foo": "bar"},
},
// Patch
{
name: "v1beta1 patch",
uid: "123",
mutating: true,
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
},
},
expectAllowed: true,
expectPatch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
expectPatchType: "JSONPatch",
},
{
name: "v1 patch",
uid: "123",
mutating: true,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
PatchType: &v1JSONPatch,
},
},
expectAllowed: true,
expectPatch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
expectPatchType: "JSONPatch",
},
// Result
{
name: "v1beta1 result",
uid: "123",
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{Status: "Failure", Message: "Foo", Code: 401},
},
},
expectAllowed: false,
expectResult: &metav1.Status{Status: "Failure", Message: "Foo", Code: 401},
},
{
name: "v1 result",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: false,
Result: &metav1.Status{Status: "Failure", Message: "Foo", Code: 401},
},
},
expectAllowed: false,
expectResult: &metav1.Status{Status: "Failure", Message: "Foo", Code: 401},
},
// Missing response
{
name: "v1beta1 no response",
uid: "123",
review: &admissionv1beta1.AdmissionReview{},
expectErr: "response was absent",
},
{
name: "v1 no response",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
},
expectErr: "response was absent",
},
// v1 invalid responses
{
name: "v1 wrong group",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io2/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
},
},
expectErr: "expected webhook response of admission.k8s.io/v1, Kind=AdmissionReview",
},
{
name: "v1 wrong version",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v2", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
},
},
expectErr: "expected webhook response of admission.k8s.io/v1, Kind=AdmissionReview",
},
{
name: "v1 wrong kind",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview2"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
},
},
expectErr: "expected webhook response of admission.k8s.io/v1, Kind=AdmissionReview",
},
{
name: "v1 wrong uid",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "1234",
Allowed: true,
},
},
expectErr: `expected response.uid="123"`,
},
{
name: "v1 patch without patch type",
uid: "123",
mutating: true,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
},
},
expectErr: `webhook returned response.patch but not response.patchType`,
},
{
name: "v1 patch type without patch",
uid: "123",
mutating: true,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
PatchType: &v1JSONPatch,
},
},
expectErr: `webhook returned response.patchType but not response.patch`,
},
{
name: "v1 empty patch type",
uid: "123",
mutating: true,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
PatchType: &emptyv1Patch,
},
},
expectErr: `webhook returned invalid response.patchType of ""`,
},
{
name: "v1 invalid patch type",
uid: "123",
mutating: true,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
PatchType: &invalidv1Patch,
},
},
expectAllowed: true,
expectPatch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
expectPatchType: invalidv1Patch, // invalid patch types are caught when the mutating dispatcher evaluates the patch
},
{
name: "v1 patch for validating webhook",
uid: "123",
mutating: false,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
},
},
expectErr: `validating webhook may not return response.patch`,
},
{
name: "v1 patch type for validating webhook",
uid: "123",
mutating: false,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
PatchType: &invalidv1Patch,
},
},
expectErr: `validating webhook may not return response.patchType`,
},
// v1beta1 invalid responses that we have to allow/fixup for compatibility
{
name: "v1beta1 wrong group/version/kind",
uid: "123",
review: &admissionv1beta1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io2/v2", Kind: "AdmissionReview2"},
Response: &admissionv1beta1.AdmissionResponse{
Allowed: true,
},
},
expectAllowed: true,
},
{
name: "v1beta1 wrong uid",
uid: "123",
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
UID: "1234",
Allowed: true,
},
},
expectAllowed: true,
},
{
name: "v1beta1 validating returns patch/patchType",
uid: "123",
mutating: false,
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
UID: "1234",
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
PatchType: &v1beta1JSONPatch,
},
},
expectAllowed: true,
},
{
name: "v1beta1 empty patch type",
uid: "123",
mutating: true,
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
PatchType: &emptyv1beta1Patch,
},
},
expectAllowed: true,
expectPatch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
expectPatchType: admissionv1.PatchTypeJSONPatch,
},
{
name: "v1beta1 invalid patchType",
uid: "123",
mutating: true,
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
PatchType: &invalidv1beta1Patch,
},
},
expectAllowed: true,
expectPatch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
expectPatchType: admissionv1.PatchTypeJSONPatch,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
result, err := VerifyAdmissionResponse(tc.uid, tc.mutating, tc.review)
if err != nil {
if len(tc.expectErr) > 0 {
if !strings.Contains(err.Error(), tc.expectErr) {
t.Errorf("expected error '%s', got %v", tc.expectErr, err)
}
} else {
t.Errorf("unexpected error %v", err)
}
return
} else if len(tc.expectErr) > 0 {
t.Errorf("expected error '%s', got none", tc.expectErr)
return
}
if e, a := tc.expectAuditAnnotations, result.AuditAnnotations; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
if e, a := tc.expectAllowed, result.Allowed; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
if e, a := tc.expectPatch, result.Patch; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
if e, a := tc.expectPatchType, result.PatchType; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
if e, a := tc.expectResult, result.Result; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
})
}
}
func TestCreateAdmissionObjects(t *testing.T) {
internalObj := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "2", Name: "myname", Namespace: "myns"}}
internalObjOld := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "1", Name: "myname", Namespace: "myns"}}
versionedObj := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "2", Name: "myname", Namespace: "myns"}}
versionedObjOld := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "1", Name: "myname", Namespace: "myns"}}
userInfo := &user.DefaultInfo{
Name: "myuser",
Groups: []string{"mygroup"},
UID: "myuid",
Extra: map[string][]string{"extrakey": {"value1", "value2"}},
}
attrs := admission.NewAttributesRecord(
internalObj.DeepCopyObject(),
internalObjOld.DeepCopyObject(),
schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
"myns",
"myname",
schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
"",
admission.Update,
&metav1.UpdateOptions{FieldManager: "foo"},
false,
userInfo,
)
testcases := []struct {
name string
attrs *generic.VersionedAttributes
invocation *generic.WebhookInvocation
expectRequest func(uid types.UID) runtime.Object
expectResponse runtime.Object
expectErr string
}{
{
name: "no supported versions",
invocation: &generic.WebhookInvocation{
Webhook: webhook.NewMutatingWebhookAccessor("mywebhook", "mycfg", &admissionregistrationv1.MutatingWebhook{}),
},
expectErr: "webhook does not accept known AdmissionReview versions",
},
{
name: "no known supported versions",
invocation: &generic.WebhookInvocation{
Webhook: webhook.NewMutatingWebhookAccessor("mywebhook", "mycfg", &admissionregistrationv1.MutatingWebhook{
AdmissionReviewVersions: []string{"vX"},
}),
},
expectErr: "webhook does not accept known AdmissionReview versions",
},
{
name: "v1",
attrs: &generic.VersionedAttributes{
VersionedObject: versionedObj.DeepCopyObject(),
VersionedOldObject: versionedObjOld.DeepCopyObject(),
Attributes: attrs,
},
invocation: &generic.WebhookInvocation{
Resource: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
Subresource: "",
Kind: schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Deployment"},
Webhook: webhook.NewMutatingWebhookAccessor("mywebhook", "mycfg", &admissionregistrationv1.MutatingWebhook{
AdmissionReviewVersions: []string{"v1", "v1beta1"},
}),
},
expectRequest: func(uid types.UID) runtime.Object {
return &admissionv1.AdmissionReview{
Request: &admissionv1.AdmissionRequest{
UID: uid,
Kind: metav1.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Deployment"},
Resource: metav1.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
SubResource: "",
RequestKind: &metav1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
RequestResource: &metav1.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
RequestSubResource: "",
Name: "myname",
Namespace: "myns",
Operation: "UPDATE",
UserInfo: authenticationv1.UserInfo{
Username: "myuser",
UID: "myuid",
Groups: []string{"mygroup"},
Extra: map[string]authenticationv1.ExtraValue{"extrakey": {"value1", "value2"}},
},
Object: runtime.RawExtension{Object: versionedObj},
OldObject: runtime.RawExtension{Object: versionedObjOld},
DryRun: utilpointer.BoolPtr(false),
Options: runtime.RawExtension{Object: &metav1.UpdateOptions{FieldManager: "foo"}},
},
}
},
expectResponse: &admissionv1.AdmissionReview{},
},
{
name: "v1beta1",
attrs: &generic.VersionedAttributes{
VersionedObject: versionedObj.DeepCopyObject(),
VersionedOldObject: versionedObjOld.DeepCopyObject(),
Attributes: attrs,
},
invocation: &generic.WebhookInvocation{
Resource: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
Subresource: "",
Kind: schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Deployment"},
Webhook: webhook.NewMutatingWebhookAccessor("mywebhook", "mycfg", &admissionregistrationv1.MutatingWebhook{
AdmissionReviewVersions: []string{"v1beta1", "v1"},
}),
},
expectRequest: func(uid types.UID) runtime.Object {
return &admissionv1beta1.AdmissionReview{
Request: &admissionv1beta1.AdmissionRequest{
UID: uid,
Kind: metav1.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Deployment"},
Resource: metav1.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
SubResource: "",
RequestKind: &metav1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
RequestResource: &metav1.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
RequestSubResource: "",
Name: "myname",
Namespace: "myns",
Operation: "UPDATE",
UserInfo: authenticationv1.UserInfo{
Username: "myuser",
UID: "myuid",
Groups: []string{"mygroup"},
Extra: map[string]authenticationv1.ExtraValue{"extrakey": {"value1", "value2"}},
},
Object: runtime.RawExtension{Object: versionedObj},
OldObject: runtime.RawExtension{Object: versionedObjOld},
DryRun: utilpointer.BoolPtr(false),
Options: runtime.RawExtension{Object: &metav1.UpdateOptions{FieldManager: "foo"}},
},
}
},
expectResponse: &admissionv1beta1.AdmissionReview{},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
uid, request, response, err := CreateAdmissionObjects(tc.attrs, tc.invocation)
if err != nil {
if len(tc.expectErr) > 0 {
if !strings.Contains(err.Error(), tc.expectErr) {
t.Errorf("expected error '%s', got %v", tc.expectErr, err)
}
} else {
t.Errorf("unexpected error %v", err)
}
return
} else if len(tc.expectErr) > 0 {
t.Errorf("expected error '%s', got none", tc.expectErr)
return
}
if len(uid) == 0 {
t.Errorf("expected uid, got none")
}
if e, a := tc.expectRequest(uid), request; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
if e, a := tc.expectResponse, response; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
})
}
}
相关信息
相关文章
0
赞
热门推荐
-
2、 - 优质文章
-
3、 gate.io
-
8、 golang
-
9、 openharmony
-
10、 Vue中input框自动聚焦