Add ObjectInterfaces to Admission and Validation

Kubernetes-commit: 513a87c7b25aa58f84fafe0dc170cee4c76e481b
This commit is contained in:
Mehdy Bohlool 2019-02-16 12:27:24 -08:00 committed by Kubernetes Publisher
parent cfef629361
commit 87b5ac0c06
26 changed files with 164 additions and 132 deletions

View File

@ -44,7 +44,7 @@ func WithAudit(i Interface, ae *auditinternal.Event) Interface {
return &auditHandler{i, ae}
}
func (handler auditHandler) Admit(a Attributes) error {
func (handler auditHandler) Admit(a Attributes, o ObjectInterfaces) error {
if !handler.Interface.Handles(a.GetOperation()) {
return nil
}
@ -53,13 +53,13 @@ func (handler auditHandler) Admit(a Attributes) error {
}
var err error
if mutator, ok := handler.Interface.(MutationInterface); ok {
err = mutator.Admit(a)
err = mutator.Admit(a, o)
handler.logAnnotations(a)
}
return err
}
func (handler auditHandler) Validate(a Attributes) error {
func (handler auditHandler) Validate(a Attributes, o ObjectInterfaces) error {
if !handler.Interface.Handles(a.GetOperation()) {
return nil
}
@ -68,7 +68,7 @@ func (handler auditHandler) Validate(a Attributes) error {
}
var err error
if validator, ok := handler.Interface.(ValidationInterface); ok {
err = validator.Validate(a)
err = validator.Validate(a, o)
handler.logAnnotations(a)
}
return err

View File

@ -45,14 +45,14 @@ var _ Interface = &fakeHandler{}
var _ MutationInterface = &fakeHandler{}
var _ ValidationInterface = &fakeHandler{}
func (h fakeHandler) Admit(a Attributes) error {
func (h fakeHandler) Admit(a Attributes, o ObjectInterfaces) error {
for k, v := range h.admitAnnotations {
a.AddAnnotation(k, v)
}
return h.admit
}
func (h fakeHandler) Validate(a Attributes) error {
func (h fakeHandler) Validate(a Attributes, o ObjectInterfaces) error {
for k, v := range h.validateAnnotations {
a.AddAnnotation(k, v)
}
@ -149,13 +149,13 @@ func TestWithAudit(t *testing.T) {
require.True(t, ok)
auditMutator, ok := auditHandler.(MutationInterface)
require.True(t, ok)
assert.Equal(t, mutator.Admit(a), auditMutator.Admit(a), tcName+": WithAudit decorator should not effect the return value")
assert.Equal(t, mutator.Admit(a, nil), auditMutator.Admit(a, nil), tcName+": WithAudit decorator should not effect the return value")
validator, ok := handler.(ValidationInterface)
require.True(t, ok)
auditValidator, ok := auditHandler.(ValidationInterface)
require.True(t, ok)
assert.Equal(t, validator.Validate(a), auditValidator.Validate(a), tcName+": WithAudit decorator should not effect the return value")
assert.Equal(t, validator.Validate(a, nil), auditValidator.Validate(a, nil), tcName+": WithAudit decorator should not effect the return value")
annotations := make(map[string]string, len(tc.admitAnnotations)+len(tc.validateAnnotations))
for k, v := range tc.admitAnnotations {

View File

@ -26,13 +26,13 @@ func NewChainHandler(handlers ...Interface) chainAdmissionHandler {
}
// Admit performs an admission control check using a chain of handlers, and returns immediately on first error
func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error {
func (admissionHandler chainAdmissionHandler) Admit(a Attributes, o ObjectInterfaces) error {
for _, handler := range admissionHandler {
if !handler.Handles(a.GetOperation()) {
continue
}
if mutator, ok := handler.(MutationInterface); ok {
err := mutator.Admit(a)
err := mutator.Admit(a, o)
if err != nil {
return err
}
@ -42,13 +42,13 @@ func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error {
}
// Validate performs an admission control check using a chain of handlers, and returns immediately on first error
func (admissionHandler chainAdmissionHandler) Validate(a Attributes) error {
func (admissionHandler chainAdmissionHandler) Validate(a Attributes, o ObjectInterfaces) error {
for _, handler := range admissionHandler {
if !handler.Handles(a.GetOperation()) {
continue
}
if validator, ok := handler.(ValidationInterface); ok {
err := validator.Validate(a)
err := validator.Validate(a, o)
if err != nil {
return err
}

View File

@ -31,7 +31,7 @@ type FakeHandler struct {
validate, validateCalled bool
}
func (h *FakeHandler) Admit(a Attributes) (err error) {
func (h *FakeHandler) Admit(a Attributes, o ObjectInterfaces) (err error) {
h.admitCalled = true
if h.admit {
return nil
@ -39,7 +39,7 @@ func (h *FakeHandler) Admit(a Attributes) (err error) {
return fmt.Errorf("Don't admit")
}
func (h *FakeHandler) Validate(a Attributes) (err error) {
func (h *FakeHandler) Validate(a Attributes, o ObjectInterfaces) (err error) {
h.validateCalled = true
if h.validate {
return nil
@ -119,7 +119,7 @@ func TestAdmitAndValidate(t *testing.T) {
for _, test := range tests {
t.Logf("testcase = %s", test.name)
// call admit and check that validate was not called at all
err := test.chain.Admit(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, false, nil))
err := test.chain.Admit(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, false, nil), nil)
accepted := (err == nil)
if accepted != test.accept {
t.Errorf("unexpected result of admit call: %v", accepted)
@ -140,7 +140,7 @@ func TestAdmitAndValidate(t *testing.T) {
}
// call validate and check that admit was not called at all
err = test.chain.Validate(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, false, nil))
err = test.chain.Validate(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, false, nil), nil)
accepted = (err == nil)
if accepted != test.accept {
t.Errorf("unexpected result of validate call: %v\n", accepted)

View File

@ -85,7 +85,9 @@ type WantExternalKubeInformerFactory struct {
func (self *WantExternalKubeInformerFactory) SetExternalKubeInformerFactory(sf informers.SharedInformerFactory) {
self.sf = sf
}
func (self *WantExternalKubeInformerFactory) Admit(a admission.Attributes) error { return nil }
func (self *WantExternalKubeInformerFactory) Admit(a admission.Attributes, o admission.ObjectInterfaces) error {
return nil
}
func (self *WantExternalKubeInformerFactory) Handles(o admission.Operation) bool { return false }
func (self *WantExternalKubeInformerFactory) ValidateInitialization() error { return nil }
@ -98,9 +100,11 @@ type WantExternalKubeClientSet struct {
}
func (self *WantExternalKubeClientSet) SetExternalKubeClientSet(cs kubernetes.Interface) { self.cs = cs }
func (self *WantExternalKubeClientSet) Admit(a admission.Attributes) error { return nil }
func (self *WantExternalKubeClientSet) Handles(o admission.Operation) bool { return false }
func (self *WantExternalKubeClientSet) ValidateInitialization() error { return nil }
func (self *WantExternalKubeClientSet) Admit(a admission.Attributes, o admission.ObjectInterfaces) error {
return nil
}
func (self *WantExternalKubeClientSet) Handles(o admission.Operation) bool { return false }
func (self *WantExternalKubeClientSet) ValidateInitialization() error { return nil }
var _ admission.Interface = &WantExternalKubeClientSet{}
var _ initializer.WantsExternalKubeClientSet = &WantExternalKubeClientSet{}
@ -111,9 +115,11 @@ type WantAuthorizerAdmission struct {
}
func (self *WantAuthorizerAdmission) SetAuthorizer(a authorizer.Authorizer) { self.auth = a }
func (self *WantAuthorizerAdmission) Admit(a admission.Attributes) error { return nil }
func (self *WantAuthorizerAdmission) Handles(o admission.Operation) bool { return false }
func (self *WantAuthorizerAdmission) ValidateInitialization() error { return nil }
func (self *WantAuthorizerAdmission) Admit(a admission.Attributes, o admission.ObjectInterfaces) error {
return nil
}
func (self *WantAuthorizerAdmission) Handles(o admission.Operation) bool { return false }
func (self *WantAuthorizerAdmission) ValidateInitialization() error { return nil }
var _ admission.Interface = &WantAuthorizerAdmission{}
var _ initializer.WantsAuthorizer = &WantAuthorizerAdmission{}
@ -130,8 +136,10 @@ type clientCertWanter struct {
gotCert, gotKey []byte
}
func (s *clientCertWanter) SetClientCert(cert, key []byte) { s.gotCert, s.gotKey = cert, key }
func (s *clientCertWanter) Admit(a admission.Attributes) error { return nil }
func (s *clientCertWanter) SetClientCert(cert, key []byte) { s.gotCert, s.gotKey = cert, key }
func (s *clientCertWanter) Admit(a admission.Attributes, o admission.ObjectInterfaces) error {
return nil
}
func (s *clientCertWanter) Handles(o admission.Operation) bool { return false }
func (s *clientCertWanter) ValidateInitialization() error { return nil }
@ -140,8 +148,10 @@ type WantSchemeAdmission struct {
scheme *runtime.Scheme
}
func (self *WantSchemeAdmission) SetScheme(s *runtime.Scheme) { self.scheme = s }
func (self *WantSchemeAdmission) Admit(a admission.Attributes) error { return nil }
func (self *WantSchemeAdmission) SetScheme(s *runtime.Scheme) { self.scheme = s }
func (self *WantSchemeAdmission) Admit(a admission.Attributes, o admission.ObjectInterfaces) error {
return nil
}
func (self *WantSchemeAdmission) Handles(o admission.Operation) bool { return false }
func (self *WantSchemeAdmission) ValidateInitialization() error { return nil }

View File

@ -62,6 +62,20 @@ type Attributes interface {
AddAnnotation(key, value string) error
}
// ObjectInterfaces is an interface used by AdmissionController to get object interfaces
// such as Converter or Defaulter. These interfaces are normally coming from Request Scope
// to handle special cases like CRDs.
type ObjectInterfaces interface {
// GetObjectCreater is the ObjectCreator appropriate for the requested object.
GetObjectCreater() runtime.ObjectCreater
// GetObjectTyper is the ObjectTyper appropriate for the requested object.
GetObjectTyper() runtime.ObjectTyper
// GetObjectDefaulter is the ObjectDefaulter appropriate for the requested object.
GetObjectDefaulter() runtime.ObjectDefaulter
// GetObjectConvertor is the ObjectConvertor appropriate for the requested object.
GetObjectConvertor() runtime.ObjectConvertor
}
// privateAnnotationsGetter is a private interface which allows users to get annotations from Attributes.
type privateAnnotationsGetter interface {
getAnnotations() map[string]string
@ -84,7 +98,7 @@ type MutationInterface interface {
Interface
// Admit makes an admission decision based on the request attributes
Admit(a Attributes) (err error)
Admit(a Attributes, o ObjectInterfaces) (err error)
}
// ValidationInterface is an abstract, pluggable interface for Admission Control decisions.
@ -92,7 +106,7 @@ type ValidationInterface interface {
Interface
// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate
Validate(a Attributes) (err error)
Validate(a Attributes, o ObjectInterfaces) (err error)
}
// Operation is the type of resource operation being checked for admission control

View File

@ -75,27 +75,27 @@ type pluginHandlerWithMetrics struct {
}
// Admit performs a mutating admission control check and emit metrics.
func (p pluginHandlerWithMetrics) Admit(a admission.Attributes) error {
func (p pluginHandlerWithMetrics) Admit(a admission.Attributes, o admission.ObjectInterfaces) error {
mutatingHandler, ok := p.Interface.(admission.MutationInterface)
if !ok {
return nil
}
start := time.Now()
err := mutatingHandler.Admit(a)
err := mutatingHandler.Admit(a, o)
p.observer(time.Since(start), err != nil, a, stepAdmit, p.extraLabels...)
return err
}
// Validate performs a non-mutating admission control check and emits metrics.
func (p pluginHandlerWithMetrics) Validate(a admission.Attributes) error {
func (p pluginHandlerWithMetrics) Validate(a admission.Attributes, o admission.ObjectInterfaces) error {
validatingHandler, ok := p.Interface.(admission.ValidationInterface)
if !ok {
return nil
}
start := time.Now()
err := validatingHandler.Validate(a)
err := validatingHandler.Validate(a, o)
p.observer(time.Since(start), err != nil, a, stepValidate, p.extraLabels...)
return err
}

View File

@ -34,8 +34,8 @@ var (
func TestObserveAdmissionStep(t *testing.T) {
Metrics.reset()
handler := WithStepMetrics(&mutatingAndValidatingFakeHandler{admission.NewHandler(admission.Create), true, true})
handler.(admission.MutationInterface).Admit(attr)
handler.(admission.ValidationInterface).Validate(attr)
handler.(admission.MutationInterface).Admit(attr, nil)
handler.(admission.ValidationInterface).Validate(attr, nil)
wantLabels := map[string]string{
"operation": string(admission.Create),
"type": "admit",
@ -52,8 +52,8 @@ func TestObserveAdmissionStep(t *testing.T) {
func TestObserveAdmissionController(t *testing.T) {
Metrics.reset()
handler := WithControllerMetrics(&mutatingAndValidatingFakeHandler{admission.NewHandler(admission.Create), true, true}, "a")
handler.(admission.MutationInterface).Admit(attr)
handler.(admission.ValidationInterface).Validate(attr)
handler.(admission.MutationInterface).Admit(attr, nil)
handler.(admission.ValidationInterface).Validate(attr, nil)
wantLabels := map[string]string{
"name": "a",
"operation": string(admission.Create),
@ -144,7 +144,7 @@ func TestWithMetrics(t *testing.T) {
h := WithMetrics(test.handler, Metrics.ObserveAdmissionController, test.name)
// test mutation
err := h.(admission.MutationInterface).Admit(admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, false, nil))
err := h.(admission.MutationInterface).Admit(admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, false, nil), nil)
if test.admit && err != nil {
t.Errorf("expected admit to succeed, but failed: %v", err)
continue
@ -169,7 +169,7 @@ func TestWithMetrics(t *testing.T) {
}
// test validation
err = h.(admission.ValidationInterface).Validate(admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, false, nil))
err = h.(admission.ValidationInterface).Validate(admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, false, nil), nil)
if test.validate && err != nil {
t.Errorf("expected admit to succeed, but failed: %v", err)
continue
@ -196,14 +196,14 @@ type mutatingAndValidatingFakeHandler struct {
validate bool
}
func (h *mutatingAndValidatingFakeHandler) Admit(a admission.Attributes) (err error) {
func (h *mutatingAndValidatingFakeHandler) Admit(a admission.Attributes, o admission.ObjectInterfaces) (err error) {
if h.admit {
return nil
}
return fmt.Errorf("don't admit")
}
func (h *mutatingAndValidatingFakeHandler) Validate(a admission.Attributes) (err error) {
func (h *mutatingAndValidatingFakeHandler) Validate(a admission.Attributes, o admission.ObjectInterfaces) (err error) {
if h.validate {
return nil
}
@ -215,7 +215,7 @@ type validatingFakeHandler struct {
validate bool
}
func (h *validatingFakeHandler) Validate(a admission.Attributes) (err error) {
func (h *validatingFakeHandler) Validate(a admission.Attributes, o admission.ObjectInterfaces) (err error) {
if h.validate {
return nil
}
@ -227,7 +227,7 @@ type mutatingFakeHandler struct {
admit bool
}
func (h *mutatingFakeHandler) Admit(a admission.Attributes) (err error) {
func (h *mutatingFakeHandler) Admit(a admission.Attributes, o admission.ObjectInterfaces) (err error) {
if h.admit {
return nil
}

View File

@ -17,39 +17,25 @@ limitations under the License.
package generic
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
)
// convertor converts objects to the desired version.
type convertor struct {
Scheme *runtime.Scheme
}
// ConvertToGVK converts object to the desired gvk.
func (c *convertor) ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind) (runtime.Object, error) {
func ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind, o admission.ObjectInterfaces) (runtime.Object, error) {
// Unlike other resources, custom resources do not have internal version, so
// if obj is a custom resource, it should not need conversion.
if obj.GetObjectKind().GroupVersionKind() == gvk {
return obj, nil
}
out, err := c.Scheme.New(gvk)
out, err := o.GetObjectCreater().New(gvk)
if err != nil {
return nil, err
}
err = c.Scheme.Convert(obj, out, nil)
err = o.GetObjectConvertor().Convert(obj, out, nil)
if err != nil {
return nil, err
}
return out, nil
}
// Validate checks if the conversion has a scheme.
func (c *convertor) Validate() error {
if c.Scheme == nil {
return fmt.Errorf("the convertor requires a scheme")
}
return nil
}

View File

@ -26,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/apis/example"
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
example2v1 "k8s.io/apiserver/pkg/apis/example2/v1"
@ -41,7 +42,7 @@ func initiateScheme(t *testing.T) *runtime.Scheme {
func TestConvertToGVK(t *testing.T) {
scheme := initiateScheme(t)
c := convertor{Scheme: scheme}
o := &admission.SchemeBasedObjectInterfaces{scheme}
table := map[string]struct {
obj runtime.Object
gvk schema.GroupVersionKind
@ -122,7 +123,7 @@ func TestConvertToGVK(t *testing.T) {
for name, test := range table {
t.Run(name, func(t *testing.T) {
actual, err := c.ConvertToGVK(test.obj, test.gvk)
actual, err := ConvertToGVK(test.obj, test.gvk, o)
if err != nil {
t.Error(err)
}

View File

@ -43,7 +43,6 @@ type Webhook struct {
hookSource Source
clientManager *webhook.ClientManager
convertor *convertor
namespaceMatcher *namespace.Matcher
dispatcher Dispatcher
}
@ -79,7 +78,6 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory
Handler: handler,
sourceFactory: sourceFactory,
clientManager: &cm,
convertor: &convertor{},
namespaceMatcher: &namespace.Matcher{},
dispatcher: dispatcherFactory(&cm),
}, nil
@ -100,9 +98,6 @@ func (a *Webhook) SetServiceResolver(sr webhook.ServiceResolver) {
// SetScheme sets a serializer(NegotiatedSerializer) which is derived from the scheme
func (a *Webhook) SetScheme(scheme *runtime.Scheme) {
if scheme != nil {
a.convertor.Scheme = scheme
}
}
// SetExternalKubeClientSet implements the WantsExternalKubeInformerFactory interface.
@ -132,9 +127,6 @@ func (a *Webhook) ValidateInitialization() error {
if err := a.clientManager.Validate(); err != nil {
return fmt.Errorf("clientManager is not properly setup: %v", err)
}
if err := a.convertor.Validate(); err != nil {
return fmt.Errorf("convertor is not properly setup: %v", err)
}
return nil
}
@ -156,7 +148,7 @@ func (a *Webhook) ShouldCallHook(h *v1beta1.Webhook, attr admission.Attributes)
}
// Dispatch is called by the downstream Validate or Admit methods.
func (a *Webhook) Dispatch(attr admission.Attributes) error {
func (a *Webhook) Dispatch(attr admission.Attributes, o admission.ObjectInterfaces) error {
if rules.IsWebhookConfigurationResource(attr) {
return nil
}
@ -188,14 +180,14 @@ func (a *Webhook) Dispatch(attr admission.Attributes) error {
Attributes: attr,
}
if oldObj := attr.GetOldObject(); oldObj != nil {
out, err := a.convertor.ConvertToGVK(oldObj, attr.GetKind())
out, err := ConvertToGVK(oldObj, attr.GetKind(), o)
if err != nil {
return apierrors.NewInternalError(err)
}
versionedAttr.VersionedOldObject = out
}
if obj := attr.GetObject(); obj != nil {
out, err := a.convertor.ConvertToGVK(obj, attr.GetKind())
out, err := ConvertToGVK(obj, attr.GetKind(), o)
if err != nil {
return apierrors.NewInternalError(err)
}

View File

@ -91,6 +91,6 @@ func (a *Plugin) ValidateInitialization() error {
}
// Admit makes an admission decision based on the request attributes.
func (a *Plugin) Admit(attr admission.Attributes) error {
return a.Webhook.Dispatch(attr)
func (a *Plugin) Admit(attr admission.Attributes, o admission.ObjectInterfaces) error {
return a.Webhook.Dispatch(attr, o)
}

View File

@ -23,23 +23,15 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/api/admission/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/admission"
webhooktesting "k8s.io/apiserver/pkg/admission/plugin/webhook/testing"
)
// TestAdmit tests that MutatingWebhook#Admit works as expected
func TestAdmit(t *testing.T) {
scheme := runtime.NewScheme()
require.NoError(t, v1beta1.AddToScheme(scheme))
require.NoError(t, corev1.AddToScheme(scheme))
testServer := webhooktesting.NewTestServer(t)
testServer.StartTLS()
defer testServer.Close()
@ -48,6 +40,8 @@ func TestAdmit(t *testing.T) {
t.Fatalf("this should never happen? %v", err)
}
objectInterfaces := webhooktesting.NewObjectInterfacesForTest()
stopCh := make(chan struct{})
defer close(stopCh)
@ -66,7 +60,6 @@ func TestAdmit(t *testing.T) {
wh.SetAuthenticationInfoResolverWrapper(webhooktesting.Wrapper(webhooktesting.NewAuthenticationInfoResolver(new(int32))))
wh.SetServiceResolver(webhooktesting.NewServiceResolver(*serverURL))
wh.SetScheme(scheme)
wh.SetExternalKubeClientSet(client)
wh.SetExternalKubeInformerFactory(informer)
@ -85,7 +78,7 @@ func TestAdmit(t *testing.T) {
attr = webhooktesting.NewAttribute(ns, tt.AdditionalLabels, tt.IsDryRun)
}
err = wh.Admit(attr)
err = wh.Admit(attr, objectInterfaces)
if tt.ExpectAllow != (err == nil) {
t.Errorf("%s: expected allowed=%v, but got err=%v", tt.Name, tt.ExpectAllow, err)
}
@ -118,10 +111,6 @@ func TestAdmit(t *testing.T) {
// TestAdmitCachedClient tests that MutatingWebhook#Admit should cache restClient
func TestAdmitCachedClient(t *testing.T) {
scheme := runtime.NewScheme()
require.NoError(t, v1beta1.AddToScheme(scheme))
require.NoError(t, corev1.AddToScheme(scheme))
testServer := webhooktesting.NewTestServer(t)
testServer.StartTLS()
defer testServer.Close()
@ -130,6 +119,8 @@ func TestAdmitCachedClient(t *testing.T) {
t.Fatalf("this should never happen? %v", err)
}
objectInterfaces := webhooktesting.NewObjectInterfacesForTest()
stopCh := make(chan struct{})
defer close(stopCh)
@ -138,7 +129,6 @@ func TestAdmitCachedClient(t *testing.T) {
t.Fatalf("Failed to create mutating webhook: %v", err)
}
wh.SetServiceResolver(webhooktesting.NewServiceResolver(*serverURL))
wh.SetScheme(scheme)
for _, tt := range webhooktesting.NewCachedClientTestcases(serverURL) {
ns := "webhook-test"
@ -158,7 +148,7 @@ func TestAdmitCachedClient(t *testing.T) {
continue
}
err = wh.Admit(webhooktesting.NewAttribute(ns, nil, false))
err = wh.Admit(webhooktesting.NewAttribute(ns, nil, false), objectInterfaces)
if tt.ExpectAllow != (err == nil) {
t.Errorf("%s: expected allowed=%v, but got err=%v", tt.Name, tt.ExpectAllow, err)
}

View File

@ -649,3 +649,10 @@ func newMatchEverythingRules() []registrationv1beta1.RuleWithOperations {
},
}}
}
// NewObjectInterfacesForTest returns an ObjectInterfaces appropriate for test cases in this file.
func NewObjectInterfacesForTest() admission.ObjectInterfaces {
scheme := runtime.NewScheme()
corev1.AddToScheme(scheme)
return &admission.SchemeBasedObjectInterfaces{scheme}
}

View File

@ -59,6 +59,6 @@ func NewValidatingAdmissionWebhook(configFile io.Reader) (*Plugin, error) {
}
// Validate makes an admission decision based on the request attributes.
func (a *Plugin) Validate(attr admission.Attributes) error {
return a.Webhook.Dispatch(attr)
func (a *Plugin) Validate(attr admission.Attributes, o admission.ObjectInterfaces) error {
return a.Webhook.Dispatch(attr, o)
}

View File

@ -22,25 +22,19 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/api/admission/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
webhooktesting "k8s.io/apiserver/pkg/admission/plugin/webhook/testing"
)
// TestValidate tests that ValidatingWebhook#Validate works as expected
func TestValidate(t *testing.T) {
scheme := runtime.NewScheme()
require.NoError(t, v1beta1.AddToScheme(scheme))
require.NoError(t, corev1.AddToScheme(scheme))
testServer := webhooktesting.NewTestServer(t)
testServer.StartTLS()
defer testServer.Close()
objectInterfaces := webhooktesting.NewObjectInterfacesForTest()
serverURL, err := url.ParseRequestURI(testServer.URL)
if err != nil {
t.Fatalf("this should never happen? %v", err)
@ -61,7 +55,6 @@ func TestValidate(t *testing.T) {
wh.SetAuthenticationInfoResolverWrapper(webhooktesting.Wrapper(webhooktesting.NewAuthenticationInfoResolver(new(int32))))
wh.SetServiceResolver(webhooktesting.NewServiceResolver(*serverURL))
wh.SetScheme(scheme)
wh.SetExternalKubeClientSet(client)
wh.SetExternalKubeInformerFactory(informer)
@ -74,7 +67,7 @@ func TestValidate(t *testing.T) {
}
attr := webhooktesting.NewAttribute(ns, nil, tt.IsDryRun)
err = wh.Validate(attr)
err = wh.Validate(attr, objectInterfaces)
if tt.ExpectAllow != (err == nil) {
t.Errorf("%s: expected allowed=%v, but got err=%v", tt.Name, tt.ExpectAllow, err)
}
@ -102,10 +95,6 @@ func TestValidate(t *testing.T) {
// TestValidateCachedClient tests that ValidatingWebhook#Validate should cache restClient
func TestValidateCachedClient(t *testing.T) {
scheme := runtime.NewScheme()
require.NoError(t, v1beta1.AddToScheme(scheme))
require.NoError(t, corev1.AddToScheme(scheme))
testServer := webhooktesting.NewTestServer(t)
testServer.StartTLS()
defer testServer.Close()
@ -114,6 +103,8 @@ func TestValidateCachedClient(t *testing.T) {
t.Fatalf("this should never happen? %v", err)
}
objectInterfaces := webhooktesting.NewObjectInterfacesForTest()
stopCh := make(chan struct{})
defer close(stopCh)
@ -122,7 +113,6 @@ func TestValidateCachedClient(t *testing.T) {
t.Fatalf("Failed to create validating webhook: %v", err)
}
wh.SetServiceResolver(webhooktesting.NewServiceResolver(*serverURL))
wh.SetScheme(scheme)
for _, tt := range webhooktesting.NewCachedClientTestcases(serverURL) {
ns := "webhook-test"
@ -142,7 +132,7 @@ func TestValidateCachedClient(t *testing.T) {
continue
}
err = wh.Validate(webhooktesting.NewAttribute(ns, nil, false))
err = wh.Validate(webhooktesting.NewAttribute(ns, nil, false), objectInterfaces)
if tt.ExpectAllow != (err == nil) {
t.Errorf("%s: expected allowed=%v, but got err=%v", tt.Name, tt.ExpectAllow, err)
}

28
pkg/admission/util.go Normal file
View File

@ -0,0 +1,28 @@
/*
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 admission
import "k8s.io/apimachinery/pkg/runtime"
type SchemeBasedObjectInterfaces struct {
Scheme *runtime.Scheme
}
func (r *SchemeBasedObjectInterfaces) GetObjectCreater() runtime.ObjectCreater { return r.Scheme }
func (r *SchemeBasedObjectInterfaces) GetObjectTyper() runtime.ObjectTyper { return r.Scheme }
func (r *SchemeBasedObjectInterfaces) GetObjectDefaulter() runtime.ObjectDefaulter { return r.Scheme }
func (r *SchemeBasedObjectInterfaces) GetObjectConvertor() runtime.ObjectConvertor { return r.Scheme }

View File

@ -76,7 +76,7 @@ import (
type alwaysMutatingDeny struct{}
func (alwaysMutatingDeny) Admit(a admission.Attributes) (err error) {
func (alwaysMutatingDeny) Admit(a admission.Attributes, o admission.ObjectInterfaces) (err error) {
return admission.NewForbidden(a, errors.New("Mutating admission control is denying all modifications"))
}
@ -86,7 +86,7 @@ func (alwaysMutatingDeny) Handles(operation admission.Operation) bool {
type alwaysValidatingDeny struct{}
func (alwaysValidatingDeny) Validate(a admission.Attributes) (err error) {
func (alwaysValidatingDeny) Validate(a admission.Attributes, o admission.ObjectInterfaces) (err error) {
return admission.NewForbidden(a, errors.New("Validating admission control is denying all modifications"))
}

View File

@ -127,7 +127,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
userInfo, _ := request.UserFrom(ctx)
admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo)
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
err = mutatingAdmission.Admit(admissionAttributes)
err = mutatingAdmission.Admit(admissionAttributes, &scope)
if err != nil {
scope.err(err, w, req)
return
@ -154,7 +154,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
ctx,
name,
obj,
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes),
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, &scope),
options,
)
})

View File

@ -119,13 +119,13 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
userInfo, _ := request.UserFrom(ctx)
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, dryrun.IsDryRun(options.DryRun), userInfo)
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
if err := mutatingAdmission.Admit(attrs); err != nil {
if err := mutatingAdmission.Admit(attrs, &scope); err != nil {
scope.err(err, w, req)
return
}
}
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
if err := validatingAdmission.Validate(attrs); err != nil {
if err := validatingAdmission.Validate(attrs, &scope); err != nil {
scope.err(err, w, req)
return
}
@ -269,7 +269,7 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
userInfo, _ := request.UserFrom(ctx)
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, dryrun.IsDryRun(options.DryRun), userInfo)
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
err = mutatingAdmission.Admit(attrs)
err = mutatingAdmission.Admit(attrs, &scope)
if err != nil {
scope.err(err, w, req)
return
@ -277,7 +277,7 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
}
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
err = validatingAdmission.Validate(attrs)
err = validatingAdmission.Validate(attrs, &scope)
if err != nil {
scope.err(err, w, req)
return

View File

@ -191,10 +191,12 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
subresource: scope.Subresource,
dryRun: dryrun.IsDryRun(options.DryRun),
objectInterfaces: &scope,
hubGroupVersion: scope.HubGroupVersion,
createValidation: withAuthorization(rest.AdmissionToValidateObjectFunc(admit, staticCreateAttributes), scope.Authorizer, createAuthorizerAttributes),
updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticUpdateAttributes),
createValidation: withAuthorization(rest.AdmissionToValidateObjectFunc(admit, staticCreateAttributes, &scope), scope.Authorizer, createAuthorizerAttributes),
updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticUpdateAttributes, &scope),
admissionCheck: mutatingAdmission,
codec: codec,
@ -257,6 +259,8 @@ type patcher struct {
subresource string
dryRun bool
objectInterfaces admission.ObjectInterfaces
hubGroupVersion schema.GroupVersion
// Validation functions
@ -507,7 +511,7 @@ func (p *patcher) applyAdmission(ctx context.Context, patchedObject runtime.Obje
}
if p.admissionCheck != nil && p.admissionCheck.Handles(operation) {
attributes := p.admissionAttributes(ctx, patchedObject, currentObject, operation)
return patchedObject, p.admissionCheck.Admit(attributes)
return patchedObject, p.admissionCheck.Admit(attributes, p.objectInterfaces)
}
return patchedObject, nil
}

View File

@ -103,6 +103,13 @@ func (scope *RequestScope) AllowsStreamSchema(s string) bool {
return s == "watch"
}
var _ admission.ObjectInterfaces = &RequestScope{}
func (r *RequestScope) GetObjectCreater() runtime.ObjectCreater { return r.Creater }
func (r *RequestScope) GetObjectTyper() runtime.ObjectTyper { return r.Typer }
func (r *RequestScope) GetObjectDefaulter() runtime.ObjectDefaulter { return r.Defaulter }
func (r *RequestScope) GetObjectConvertor() runtime.ObjectConvertor { return r.Convertor }
// ConnectResource returns a function that handles a connect request on a rest.Storage object.
func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admission.Interface, restPath string, isSubresource bool) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
@ -131,14 +138,14 @@ func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admissi
userInfo, _ := request.UserFrom(ctx)
// TODO: remove the mutating admission here as soon as we have ported all plugin that handle CONNECT
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
err = mutatingAdmission.Admit(admission.NewAttributesRecord(opts, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, false, userInfo))
err = mutatingAdmission.Admit(admission.NewAttributesRecord(opts, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, false, userInfo), &scope)
if err != nil {
scope.err(err, w, req)
return
}
}
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
err = validatingAdmission.Validate(admission.NewAttributesRecord(opts, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, false, userInfo))
err = validatingAdmission.Validate(admission.NewAttributesRecord(opts, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, false, userInfo), &scope)
if err != nil {
scope.err(err, w, req)
return

View File

@ -365,6 +365,7 @@ func (tc *patchTestCase) Run(t *testing.T) {
creater := runtime.ObjectCreater(scheme)
defaulter := runtime.ObjectDefaulter(scheme)
convertor := runtime.UnsafeObjectConvertor(scheme)
objectInterfaces := &admission.SchemeBasedObjectInterfaces{scheme}
kind := examplev1.SchemeGroupVersion.WithKind("Pod")
resource := examplev1.SchemeGroupVersion.WithResource("pods")
schemaReferenceObj := &examplev1.Pod{}
@ -441,6 +442,8 @@ func (tc *patchTestCase) Run(t *testing.T) {
kind: kind,
resource: resource,
objectInterfaces: objectInterfaces,
hubGroupVersion: hubVersion,
createValidation: rest.ValidateAllObjectFunc,
@ -944,6 +947,6 @@ func (f mutateObjectUpdateFunc) Handles(operation admission.Operation) bool {
return true
}
func (f mutateObjectUpdateFunc) Admit(a admission.Attributes) (err error) {
func (f mutateObjectUpdateFunc) Admit(a admission.Attributes, o admission.ObjectInterfaces) (err error) {
return f(a.GetObject(), a.GetOldObject())
}

View File

@ -138,11 +138,11 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
return nil, fmt.Errorf("unexpected error when extracting UID from oldObj: %v", err.Error())
} else if !isNotZeroObject {
if mutatingAdmission.Handles(admission.Create) {
return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo))
return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo), &scope)
}
} else {
if mutatingAdmission.Handles(admission.Update) {
return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, dryrun.IsDryRun(options.DryRun), userInfo))
return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, dryrun.IsDryRun(options.DryRun), userInfo), &scope)
}
}
return newObj, nil
@ -172,11 +172,11 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
rest.DefaultUpdatedObjectInfo(obj, transformers...),
withAuthorization(rest.AdmissionToValidateObjectFunc(
admit,
admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo)),
admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo), &scope),
scope.Authorizer, createAuthorizerAttributes),
rest.AdmissionToValidateObjectUpdateFunc(
admit,
admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, dryrun.IsDryRun(options.DryRun), userInfo)),
admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, dryrun.IsDryRun(options.DryRun), userInfo), &scope),
false,
options,
)

View File

@ -160,7 +160,7 @@ type NamespaceScopedStrategy interface {
}
// AdmissionToValidateObjectFunc converts validating admission to a rest validate object func
func AdmissionToValidateObjectFunc(admit admission.Interface, staticAttributes admission.Attributes) ValidateObjectFunc {
func AdmissionToValidateObjectFunc(admit admission.Interface, staticAttributes admission.Attributes, o admission.ObjectInterfaces) ValidateObjectFunc {
validatingAdmission, ok := admit.(admission.ValidationInterface)
if !ok {
return func(obj runtime.Object) error { return nil }
@ -181,6 +181,6 @@ func AdmissionToValidateObjectFunc(admit admission.Interface, staticAttributes a
if !validatingAdmission.Handles(finalAttributes.GetOperation()) {
return nil
}
return validatingAdmission.Validate(finalAttributes)
return validatingAdmission.Validate(finalAttributes, o)
}
}

View File

@ -256,7 +256,7 @@ func (i *wrappedUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj run
}
// AdmissionToValidateObjectUpdateFunc converts validating admission to a rest validate object update func
func AdmissionToValidateObjectUpdateFunc(admit admission.Interface, staticAttributes admission.Attributes) ValidateObjectUpdateFunc {
func AdmissionToValidateObjectUpdateFunc(admit admission.Interface, staticAttributes admission.Attributes, o admission.ObjectInterfaces) ValidateObjectUpdateFunc {
validatingAdmission, ok := admit.(admission.ValidationInterface)
if !ok {
return func(obj, old runtime.Object) error { return nil }
@ -277,6 +277,6 @@ func AdmissionToValidateObjectUpdateFunc(admit admission.Interface, staticAttrib
if !validatingAdmission.Handles(finalAttributes.GetOperation()) {
return nil
}
return validatingAdmission.Validate(finalAttributes)
return validatingAdmission.Validate(finalAttributes, o)
}
}