Merge pull request #54484 from sttts/sttts-split-psp
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. admission: wire through validating-only admission Based on https://github.com/kubernetes/kubernetes/pull/54232. This is important and required for beta because it affects the shape of the webhook admission plugins that are going to be produced and is needed to make sure that our existing chain continues to properly verify and protect the API objects based on their final state after webhook admission mutators run. We discussed this in the October 11 API machinery call with @erictune and @caesarxuchao and we agreed to do this as a requirement for beta. See this part of the recording: https://www.youtube.com/watch?v=mrgDPHyr4VY#t=325 . Kubernetes-commit: 40212c17cdf4d7bc2a45c495cf0d37ebab032578
This commit is contained in:
commit
c9df2bdd6c
|
|
@ -30,9 +30,27 @@ func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error {
|
|||
if !handler.Handles(a.GetOperation()) {
|
||||
continue
|
||||
}
|
||||
err := handler.Admit(a)
|
||||
if err != nil {
|
||||
return err
|
||||
if mutator, ok := handler.(MutationInterface); ok {
|
||||
err := mutator.Admit(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate performs an admission control check using a chain of handlers, and returns immediately on first error
|
||||
func (admissionHandler chainAdmissionHandler) Validate(a Attributes) error {
|
||||
for _, handler := range admissionHandler {
|
||||
if !handler.Handles(a.GetOperation()) {
|
||||
continue
|
||||
}
|
||||
if validator, ok := handler.(ValidationInterface); ok {
|
||||
err := validator.Validate(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ import (
|
|||
|
||||
type FakeHandler struct {
|
||||
*Handler
|
||||
name string
|
||||
admit bool
|
||||
admitCalled bool
|
||||
name string
|
||||
admit, admitCalled bool
|
||||
validate, validateCalled bool
|
||||
}
|
||||
|
||||
func (h *FakeHandler) Admit(a Attributes) (err error) {
|
||||
|
|
@ -38,6 +38,14 @@ func (h *FakeHandler) Admit(a Attributes) (err error) {
|
|||
return fmt.Errorf("Don't admit")
|
||||
}
|
||||
|
||||
func (h *FakeHandler) Validate(a Attributes) (err error) {
|
||||
h.admitCalled = true
|
||||
if h.admit {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Don't admit")
|
||||
}
|
||||
|
||||
func makeHandler(name string, admit bool, ops ...Operation) Interface {
|
||||
return &FakeHandler{
|
||||
name: name,
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ func (self *WantExternalKubeInformerFactory) SetExternalKubeInformerFactory(sf i
|
|||
}
|
||||
func (self *WantExternalKubeInformerFactory) Admit(a admission.Attributes) error { return nil }
|
||||
func (self *WantExternalKubeInformerFactory) Handles(o admission.Operation) bool { return false }
|
||||
func (self *WantExternalKubeInformerFactory) Validate() error { return nil }
|
||||
func (self *WantExternalKubeInformerFactory) ValidateInitialization() error { return nil }
|
||||
|
||||
var _ admission.Interface = &WantExternalKubeInformerFactory{}
|
||||
var _ initializer.WantsExternalKubeInformerFactory = &WantExternalKubeInformerFactory{}
|
||||
|
|
@ -112,7 +112,7 @@ 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) Validate() error { return nil }
|
||||
func (self *WantExternalKubeClientSet) ValidateInitialization() error { return nil }
|
||||
|
||||
var _ admission.Interface = &WantExternalKubeClientSet{}
|
||||
var _ initializer.WantsExternalKubeClientSet = &WantExternalKubeClientSet{}
|
||||
|
|
@ -125,7 +125,7 @@ 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) Validate() error { return nil }
|
||||
func (self *WantAuthorizerAdmission) ValidateInitialization() error { return nil }
|
||||
|
||||
var _ admission.Interface = &WantAuthorizerAdmission{}
|
||||
var _ initializer.WantsAuthorizer = &WantAuthorizerAdmission{}
|
||||
|
|
@ -145,7 +145,7 @@ type clientCertWanter struct {
|
|||
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) Handles(o admission.Operation) bool { return false }
|
||||
func (s *clientCertWanter) Validate() error { return nil }
|
||||
func (s *clientCertWanter) ValidateInitialization() error { return nil }
|
||||
|
||||
// WantSchemeAdmission is a test stub that fulfills the WantsScheme interface.
|
||||
type WantSchemeAdmission struct {
|
||||
|
|
@ -155,7 +155,7 @@ type WantSchemeAdmission struct {
|
|||
func (self *WantSchemeAdmission) SetScheme(s *runtime.Scheme) { self.scheme = s }
|
||||
func (self *WantSchemeAdmission) Admit(a admission.Attributes) error { return nil }
|
||||
func (self *WantSchemeAdmission) Handles(o admission.Operation) bool { return false }
|
||||
func (self *WantSchemeAdmission) Validate() error { return nil }
|
||||
func (self *WantSchemeAdmission) ValidateInitialization() error { return nil }
|
||||
|
||||
var _ admission.Interface = &WantSchemeAdmission{}
|
||||
var _ initializer.WantsScheme = &WantSchemeAdmission{}
|
||||
|
|
|
|||
|
|
@ -27,23 +27,23 @@ import (
|
|||
// WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it
|
||||
type WantsExternalKubeClientSet interface {
|
||||
SetExternalKubeClientSet(kubernetes.Interface)
|
||||
admission.Validator
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsExternalKubeInformerFactory defines a function which sets InformerFactory for admission plugins that need it
|
||||
type WantsExternalKubeInformerFactory interface {
|
||||
SetExternalKubeInformerFactory(informers.SharedInformerFactory)
|
||||
admission.Validator
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsAuthorizer defines a function which sets Authorizer for admission plugins that need it.
|
||||
type WantsAuthorizer interface {
|
||||
SetAuthorizer(authorizer.Authorizer)
|
||||
admission.Validator
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsScheme defines a function that accepts runtime.Scheme for admission plugins that need it.
|
||||
type WantsScheme interface {
|
||||
SetScheme(*runtime.Scheme)
|
||||
admission.Validator
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,14 +53,26 @@ type Attributes interface {
|
|||
|
||||
// Interface is an abstract, pluggable interface for Admission Control decisions.
|
||||
type Interface interface {
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
Admit(a Attributes) (err error)
|
||||
|
||||
// Handles returns true if this admission controller can handle the given operation
|
||||
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
|
||||
Handles(operation Operation) bool
|
||||
}
|
||||
|
||||
type MutationInterface interface {
|
||||
Interface
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
Admit(a Attributes) (err error)
|
||||
}
|
||||
|
||||
// ValidationInterface is an abstract, pluggable interface for Admission Control decisions.
|
||||
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)
|
||||
}
|
||||
|
||||
// Operation is the type of resource operation being checked for admission control
|
||||
type Operation string
|
||||
|
||||
|
|
@ -78,10 +90,10 @@ type PluginInitializer interface {
|
|||
Initialize(plugin Interface)
|
||||
}
|
||||
|
||||
// Validator holds Validate functions, which are responsible for validation of initialized shared resources
|
||||
// and should be implemented on admission plugins
|
||||
type Validator interface {
|
||||
Validate() error
|
||||
// InitializationValidator holds ValidateInitialization functions, which are responsible for validation of initialized
|
||||
// shared resources and should be implemented on admission plugins
|
||||
type InitializationValidator interface {
|
||||
ValidateInitialization() error
|
||||
}
|
||||
|
||||
// ConfigProvider provides a way to get configuration for an admission plugin based on its name
|
||||
|
|
|
|||
|
|
@ -74,8 +74,8 @@ func NewInitializer() admission.Interface {
|
|||
return &initializer{}
|
||||
}
|
||||
|
||||
// Validate implements the Validator interface.
|
||||
func (i *initializer) Validate() error {
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (i *initializer) ValidateInitialization() error {
|
||||
if i.config == nil {
|
||||
return fmt.Errorf("the Initializer admission plugin requires a Kubernetes client to be provided")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,9 +57,9 @@ func Register(plugins *admission.Plugins) {
|
|||
})
|
||||
}
|
||||
|
||||
// lifecycle is an implementation of admission.Interface.
|
||||
// Lifecycle is an implementation of admission.Interface.
|
||||
// It enforces life-cycle constraints around a Namespace depending on its Phase
|
||||
type lifecycle struct {
|
||||
type Lifecycle struct {
|
||||
*admission.Handler
|
||||
client kubernetes.Interface
|
||||
immortalNamespaces sets.String
|
||||
|
|
@ -73,8 +73,8 @@ type forceLiveLookupEntry struct {
|
|||
expiry time.Time
|
||||
}
|
||||
|
||||
var _ = initializer.WantsExternalKubeInformerFactory(&lifecycle{})
|
||||
var _ = initializer.WantsExternalKubeClientSet(&lifecycle{})
|
||||
var _ = initializer.WantsExternalKubeInformerFactory(&Lifecycle{})
|
||||
var _ = initializer.WantsExternalKubeClientSet(&Lifecycle{})
|
||||
|
||||
func makeNamespaceKey(namespace string) *v1.Namespace {
|
||||
return &v1.Namespace{
|
||||
|
|
@ -85,7 +85,7 @@ func makeNamespaceKey(namespace string) *v1.Namespace {
|
|||
}
|
||||
}
|
||||
|
||||
func (l *lifecycle) Admit(a admission.Attributes) error {
|
||||
func (l *Lifecycle) Admit(a admission.Attributes) error {
|
||||
// prevent deletion of immortal namespaces
|
||||
if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
|
||||
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
|
||||
|
|
@ -188,14 +188,14 @@ func (l *lifecycle) Admit(a admission.Attributes) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// NewLifecycle creates a new namespace lifecycle admission control handler
|
||||
func NewLifecycle(immortalNamespaces sets.String) (admission.Interface, error) {
|
||||
// NewLifecycle creates a new namespace Lifecycle admission control handler
|
||||
func NewLifecycle(immortalNamespaces sets.String) (*Lifecycle, error) {
|
||||
return newLifecycleWithClock(immortalNamespaces, clock.RealClock{})
|
||||
}
|
||||
|
||||
func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (admission.Interface, error) {
|
||||
func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (*Lifecycle, error) {
|
||||
forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock)
|
||||
return &lifecycle{
|
||||
return &Lifecycle{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
|
||||
immortalNamespaces: immortalNamespaces,
|
||||
forceLiveLookupCache: forceLiveLookupCache,
|
||||
|
|
@ -203,19 +203,19 @@ func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock
|
|||
}
|
||||
|
||||
// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
|
||||
func (l *lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
func (l *Lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
namespaceInformer := f.Core().V1().Namespaces()
|
||||
l.namespaceLister = namespaceInformer.Lister()
|
||||
l.SetReadyFunc(namespaceInformer.Informer().HasSynced)
|
||||
}
|
||||
|
||||
// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
|
||||
func (l *lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) {
|
||||
func (l *Lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) {
|
||||
l.client = client
|
||||
}
|
||||
|
||||
// Validate implement the Validator interface.
|
||||
func (l *lifecycle) Validate() error {
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (l *Lifecycle) ValidateInitialization() error {
|
||||
if l.namespaceLister == nil {
|
||||
return fmt.Errorf("missing namespaceLister")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,12 +37,12 @@ import (
|
|||
)
|
||||
|
||||
// newHandlerForTest returns a configured handler for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) {
|
||||
func newHandlerForTest(c clientset.Interface) (*Lifecycle, informers.SharedInformerFactory, error) {
|
||||
return newHandlerForTestWithClock(c, clock.RealClock{})
|
||||
}
|
||||
|
||||
// newHandlerForTestWithClock returns a configured handler for testing.
|
||||
func newHandlerForTestWithClock(c clientset.Interface, cacheClock clock.Clock) (admission.Interface, informers.SharedInformerFactory, error) {
|
||||
func newHandlerForTestWithClock(c clientset.Interface, cacheClock clock.Clock) (*Lifecycle, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
|
||||
handler, err := newLifecycleWithClock(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem), cacheClock)
|
||||
if err != nil {
|
||||
|
|
@ -53,7 +53,7 @@ func newHandlerForTestWithClock(c clientset.Interface, cacheClock clock.Clock) (
|
|||
return handler, f, err
|
||||
}
|
||||
pluginInitializer.Initialize(handler)
|
||||
err = admission.Validate(handler)
|
||||
err = admission.ValidateInitialization(handler)
|
||||
return handler, f, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -156,9 +156,8 @@ func (a *GenericAdmissionWebhook) SetExternalKubeClientSet(client clientset.Inte
|
|||
a.hookSource = configuration.NewExternalAdmissionHookConfigurationManager(client.Admissionregistration().ExternalAdmissionHookConfigurations())
|
||||
}
|
||||
|
||||
// Validator holds Validate functions, which are responsible for validation of initialized shared resources
|
||||
// and should be implemented on admission plugins
|
||||
func (a *GenericAdmissionWebhook) Validate() error {
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (a *GenericAdmissionWebhook) ValidateInitialization() error {
|
||||
if a.hookSource == nil {
|
||||
return fmt.Errorf("the GenericAdmissionWebhook admission plugin requires a Kubernetes client to be provided")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,18 +156,18 @@ func (ps *Plugins) InitPlugin(name string, config io.Reader, pluginInitializer P
|
|||
|
||||
pluginInitializer.Initialize(plugin)
|
||||
// ensure that plugins have been properly initialized
|
||||
if err := Validate(plugin); err != nil {
|
||||
if err := ValidateInitialization(plugin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
// Validate will call the Validate function in each plugin if they implement
|
||||
// the Validator interface.
|
||||
func Validate(plugin Interface) error {
|
||||
if validater, ok := plugin.(Validator); ok {
|
||||
err := validater.Validate()
|
||||
// ValidateInitialization will call the InitializationValidate function in each plugin if they implement
|
||||
// the InitializationValidator interface.
|
||||
func ValidateInitialization(plugin Interface) error {
|
||||
if validater, ok := plugin.(InitializationValidator); ok {
|
||||
err := validater.ValidateInitialization()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -519,7 +519,7 @@ func (storage *SimpleRESTStorage) NewList() runtime.Object {
|
|||
return &genericapitesting.SimpleList{}
|
||||
}
|
||||
|
||||
func (storage *SimpleRESTStorage) Create(ctx request.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) {
|
||||
func (storage *SimpleRESTStorage) Create(ctx request.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
|
||||
storage.checkContext(ctx)
|
||||
storage.created = obj.(*genericapitesting.Simple)
|
||||
if err := storage.errors["create"]; err != nil {
|
||||
|
|
@ -532,7 +532,7 @@ func (storage *SimpleRESTStorage) Create(ctx request.Context, obj runtime.Object
|
|||
return obj, err
|
||||
}
|
||||
|
||||
func (storage *SimpleRESTStorage) Update(ctx request.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) {
|
||||
func (storage *SimpleRESTStorage) Update(ctx request.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) {
|
||||
storage.checkContext(ctx)
|
||||
obj, err := objInfo.UpdatedObject(ctx, &storage.item)
|
||||
if err != nil {
|
||||
|
|
@ -714,7 +714,7 @@ type NamedCreaterRESTStorage struct {
|
|||
createdName string
|
||||
}
|
||||
|
||||
func (storage *NamedCreaterRESTStorage) Create(ctx request.Context, name string, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) {
|
||||
func (storage *NamedCreaterRESTStorage) Create(ctx request.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
|
||||
storage.checkContext(ctx)
|
||||
storage.created = obj.(*genericapitesting.Simple)
|
||||
storage.createdName = name
|
||||
|
|
|
|||
|
|
@ -93,10 +93,10 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object
|
|||
ae := request.AuditEventFrom(ctx)
|
||||
audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
|
||||
|
||||
if admit != nil && admit.Handles(admission.Create) {
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
|
||||
err = admit.Admit(admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo))
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo)
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
|
||||
err = mutatingAdmission.Admit(admissionAttributes)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -108,7 +108,13 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object
|
|||
|
||||
trace.Step("About to store object in database")
|
||||
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
||||
return r.Create(ctx, name, obj, includeUninitialized)
|
||||
return r.Create(
|
||||
ctx,
|
||||
name,
|
||||
obj,
|
||||
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes),
|
||||
includeUninitialized,
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
|
|
@ -144,19 +150,19 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object
|
|||
}
|
||||
|
||||
// CreateNamedResource returns a function that will handle a resource creation with name.
|
||||
func CreateNamedResource(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) http.HandlerFunc {
|
||||
return createHandler(r, scope, typer, admit, true)
|
||||
func CreateNamedResource(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admission admission.Interface) http.HandlerFunc {
|
||||
return createHandler(r, scope, typer, admission, true)
|
||||
}
|
||||
|
||||
// CreateResource returns a function that will handle a resource creation.
|
||||
func CreateResource(r rest.Creater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) http.HandlerFunc {
|
||||
return createHandler(&namedCreaterAdapter{r}, scope, typer, admit, false)
|
||||
func CreateResource(r rest.Creater, scope RequestScope, typer runtime.ObjectTyper, admission admission.Interface) http.HandlerFunc {
|
||||
return createHandler(&namedCreaterAdapter{r}, scope, typer, admission, false)
|
||||
}
|
||||
|
||||
type namedCreaterAdapter struct {
|
||||
rest.Creater
|
||||
}
|
||||
|
||||
func (c *namedCreaterAdapter) Create(ctx request.Context, name string, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) {
|
||||
return c.Creater.Create(ctx, obj, includeUninitialized)
|
||||
func (c *namedCreaterAdapter) Create(ctx request.Context, name string, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
|
||||
return c.Creater.Create(ctx, obj, createValidatingAdmission, includeUninitialized)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import (
|
|||
)
|
||||
|
||||
// DeleteResource returns a function that will handle a resource deletion
|
||||
// TODO admission here becomes solely validating admission
|
||||
func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestScope, admit admission.Interface) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
// For performance tracking purposes.
|
||||
|
|
@ -95,11 +96,18 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
|||
trace.Step("About to check admission control")
|
||||
if admit != nil && admit.Handles(admission.Delete) {
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
|
||||
err = admit.Admit(admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo))
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo)
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
|
||||
if err := mutatingAdmission.Admit(attrs); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
|
||||
if err := validatingAdmission.Validate(attrs); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,10 +179,20 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
|||
ctx := scope.ContextFunc(req)
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
if admit != nil && admit.Handles(admission.Delete) {
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Delete) {
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
|
||||
err = admit.Admit(admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, userInfo))
|
||||
err = mutatingAdmission.Admit(admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, userInfo))
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
// TODO: avoid calling Handles twice
|
||||
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok && validatingAdmission.Handles(admission.Delete) {
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
|
||||
err = validatingAdmission.Validate(admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, userInfo))
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -92,16 +92,25 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
|||
scope.Serializer.DecoderToVersion(s.Serializer, schema.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}),
|
||||
)
|
||||
|
||||
updateAdmit := func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
if admit != nil && admit.Handles(admission.Update) {
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
return admit.Admit(admission.NewAttributesRecord(updatedObject, currentObject, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo))
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
staticAdmissionAttributes := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)
|
||||
updateMutation := func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && admit.Handles(admission.Update) {
|
||||
return mutatingAdmission.Admit(admission.NewAttributesRecord(updatedObject, currentObject, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err := patchResource(ctx, updateAdmit, timeout, versionedObj, r, name, patchType, patchJS,
|
||||
result, err := patchResource(
|
||||
ctx,
|
||||
updateMutation,
|
||||
rest.AdmissionToValidateObjectFunc(admit, staticAdmissionAttributes),
|
||||
rest.AdmissionToValidateObjectUpdateFunc(admit, staticAdmissionAttributes),
|
||||
timeout, versionedObj,
|
||||
r,
|
||||
name,
|
||||
patchType,
|
||||
patchJS,
|
||||
scope.Namer, scope.Creater, scope.Defaulter, scope.UnsafeConvertor, scope.Kind, scope.Resource, codec)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
|
|
@ -122,12 +131,14 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
|||
}
|
||||
}
|
||||
|
||||
type updateAdmissionFunc func(updatedObject runtime.Object, currentObject runtime.Object) error
|
||||
type mutateObjectUpdateFunc func(obj, old runtime.Object) error
|
||||
|
||||
// patchResource divides PatchResource for easier unit testing
|
||||
func patchResource(
|
||||
ctx request.Context,
|
||||
admit updateAdmissionFunc,
|
||||
updateMutation mutateObjectUpdateFunc,
|
||||
createValidation rest.ValidateObjectFunc,
|
||||
updateValidation rest.ValidateObjectUpdateFunc,
|
||||
timeout time.Duration,
|
||||
versionedObj runtime.Object,
|
||||
patcher rest.Patcher,
|
||||
|
|
@ -341,16 +352,15 @@ func patchResource(
|
|||
// applyAdmission is called every time GuaranteedUpdate asks for the updated object,
|
||||
// and is given the currently persisted object and the patched object as input.
|
||||
applyAdmission := func(ctx request.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) {
|
||||
return patchedObject, admit(patchedObject, currentObject)
|
||||
return patchedObject, updateMutation(patchedObject, currentObject)
|
||||
}
|
||||
|
||||
updatedObjectInfo := rest.DefaultUpdatedObjectInfo(nil, applyPatch, applyAdmission)
|
||||
|
||||
return finishRequest(timeout, func() (runtime.Object, error) {
|
||||
updateObject, _, updateErr := patcher.Update(ctx, name, updatedObjectInfo)
|
||||
updateObject, _, updateErr := patcher.Update(ctx, name, updatedObjectInfo, createValidation, updateValidation)
|
||||
for i := 0; i < MaxRetryWhenPatchConflicts && (errors.IsConflict(updateErr)); i++ {
|
||||
lastConflictErr = updateErr
|
||||
updateObject, _, updateErr = patcher.Update(ctx, name, updatedObjectInfo)
|
||||
updateObject, _, updateErr = patcher.Update(ctx, name, updatedObjectInfo, createValidation, updateValidation)
|
||||
}
|
||||
return updateObject, updateErr
|
||||
})
|
||||
|
|
|
|||
|
|
@ -117,11 +117,20 @@ func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admissi
|
|||
ResourcePath: restPath,
|
||||
}
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
|
||||
err = admit.Admit(admission.NewAttributesRecord(connectRequest, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, userInfo))
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
// TODO: remove the mutating admission here as soon as we have ported all plugin that handle CONNECT
|
||||
if mutatingAdmit, ok := admit.(admission.MutationInterface); ok {
|
||||
err = mutatingAdmit.Admit(admission.NewAttributesRecord(connectRequest, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, userInfo))
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
if mutatingAdmit, ok := admit.(admission.ValidationInterface); ok {
|
||||
err = mutatingAdmit.Validate(admission.NewAttributesRecord(connectRequest, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, userInfo))
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
requestInfo, _ := request.RequestInfoFrom(ctx)
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ func (p *testPatcher) New() runtime.Object {
|
|||
return &example.Pod{}
|
||||
}
|
||||
|
||||
func (p *testPatcher) Update(ctx request.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) {
|
||||
func (p *testPatcher) Update(ctx request.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) {
|
||||
currentPod := p.startingPod
|
||||
if p.numUpdates > 0 {
|
||||
currentPod = p.updatePod
|
||||
|
|
@ -235,7 +235,8 @@ type patchTestCase struct {
|
|||
name string
|
||||
|
||||
// admission chain to use, nil is fine
|
||||
admit updateAdmissionFunc
|
||||
admissionMutation mutateObjectUpdateFunc
|
||||
admissionValidation rest.ValidateObjectUpdateFunc // TODO: add test for this
|
||||
|
||||
// startingPod is used as the starting point for the first Update
|
||||
startingPod *example.Pod
|
||||
|
|
@ -257,9 +258,16 @@ func (tc *patchTestCase) Run(t *testing.T) {
|
|||
name := tc.startingPod.Name
|
||||
|
||||
codec := codecs.LegacyCodec(examplev1.SchemeGroupVersion)
|
||||
admit := tc.admit
|
||||
if admit == nil {
|
||||
admit = func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
|
||||
admissionMutation := tc.admissionMutation
|
||||
if admissionMutation == nil {
|
||||
admissionMutation = func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
admissionValidation := tc.admissionValidation
|
||||
if admissionValidation == nil {
|
||||
admissionValidation = func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -321,7 +329,18 @@ func (tc *patchTestCase) Run(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
resultObj, err := patchResource(ctx, admit, 1*time.Second, versionedObj, testPatcher, name, patchType, patch, namer, creater, defaulter, convertor, kind, resource, codec)
|
||||
resultObj, err := patchResource(
|
||||
ctx,
|
||||
admissionMutation,
|
||||
rest.ValidateAllObjectFunc,
|
||||
admissionValidation,
|
||||
1*time.Second,
|
||||
versionedObj,
|
||||
testPatcher,
|
||||
name,
|
||||
patchType,
|
||||
patch,
|
||||
namer, creater, defaulter, convertor, kind, resource, codec)
|
||||
if len(tc.expectedError) != 0 {
|
||||
if err == nil || err.Error() != tc.expectedError {
|
||||
t.Errorf("%s: expected error %v, but got %v", tc.name, tc.expectedError, err)
|
||||
|
|
@ -544,7 +563,7 @@ func TestPatchWithAdmissionRejection(t *testing.T) {
|
|||
tc := &patchTestCase{
|
||||
name: "TestPatchWithAdmissionRejection",
|
||||
|
||||
admit: func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
admissionMutation: func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
return errors.New("admission failure")
|
||||
},
|
||||
|
||||
|
|
@ -583,7 +602,7 @@ func TestPatchWithVersionConflictThenAdmissionFailure(t *testing.T) {
|
|||
tc := &patchTestCase{
|
||||
name: "TestPatchWithVersionConflictThenAdmissionFailure",
|
||||
|
||||
admit: func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
admissionMutation: func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
if seen {
|
||||
return errors.New("admission failure")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,18 +86,25 @@ func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectType
|
|||
return
|
||||
}
|
||||
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
staticAdmissionAttributes := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)
|
||||
var transformers []rest.TransformFunc
|
||||
if admit != nil && admit.Handles(admission.Update) {
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Update) {
|
||||
transformers = append(transformers, func(ctx request.Context, newObj, oldObj runtime.Object) (runtime.Object, error) {
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
return newObj, admit.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo))
|
||||
return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo))
|
||||
})
|
||||
}
|
||||
|
||||
trace.Step("About to store object in database")
|
||||
wasCreated := false
|
||||
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
||||
obj, created, err := r.Update(ctx, name, rest.DefaultUpdatedObjectInfo(obj, transformers...))
|
||||
obj, created, err := r.Update(
|
||||
ctx,
|
||||
name,
|
||||
rest.DefaultUpdatedObjectInfo(obj, transformers...),
|
||||
rest.AdmissionToValidateObjectFunc(admit, staticAdmissionAttributes),
|
||||
rest.AdmissionToValidateObjectUpdateFunc(admit, staticAdmissionAttributes),
|
||||
)
|
||||
wasCreated = created
|
||||
return obj, err
|
||||
})
|
||||
|
|
|
|||
|
|
@ -148,11 +148,13 @@ type Store struct {
|
|||
// AfterCreate implements a further operation to run after a resource is
|
||||
// created and before it is decorated, optional.
|
||||
AfterCreate ObjectFunc
|
||||
|
||||
// UpdateStrategy implements resource-specific behavior during updates.
|
||||
UpdateStrategy rest.RESTUpdateStrategy
|
||||
// AfterUpdate implements a further operation to run after a resource is
|
||||
// updated and before it is decorated, optional.
|
||||
AfterUpdate ObjectFunc
|
||||
|
||||
// DeleteStrategy implements resource-specific behavior during deletion.
|
||||
DeleteStrategy rest.RESTDeleteStrategy
|
||||
// AfterDelete implements a further operation to run after a resource is
|
||||
|
|
@ -303,10 +305,18 @@ func (e *Store) ListPredicate(ctx genericapirequest.Context, p storage.Selection
|
|||
}
|
||||
|
||||
// Create inserts a new item according to the unique key from the object.
|
||||
func (e *Store) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) {
|
||||
func (e *Store) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
|
||||
if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// at this point we have a fully formed object. It is time to call the validators that the apiserver
|
||||
// handling chain wants to enforce.
|
||||
if createValidation != nil {
|
||||
if err := createValidation(obj.DeepCopyObject()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
name, err := e.ObjectNameFunc(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -494,7 +504,7 @@ func (e *Store) deleteWithoutFinalizers(ctx genericapirequest.Context, name, key
|
|||
// Update performs an atomic update and set of the object. Returns the result of the update
|
||||
// or an error. If the registry allows create-on-update, the create flow will be executed.
|
||||
// A bool is returned along with the object and any errors, to indicate object creation.
|
||||
func (e *Store) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) {
|
||||
func (e *Store) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) {
|
||||
key, err := e.KeyFunc(ctx, name)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
|
|
@ -544,10 +554,18 @@ func (e *Store) Update(ctx genericapirequest.Context, name string, objInfo rest.
|
|||
if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// at this point we have a fully formed object. It is time to call the validators that the apiserver
|
||||
// handling chain wants to enforce.
|
||||
if createValidation != nil {
|
||||
if err := createValidation(obj.DeepCopyObject()); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
ttl, err := e.calculateTTL(obj, 0, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return obj, &ttl, nil
|
||||
}
|
||||
|
||||
|
|
@ -582,6 +600,13 @@ func (e *Store) Update(ctx genericapirequest.Context, name string, objInfo rest.
|
|||
if err := rest.BeforeUpdate(e.UpdateStrategy, ctx, obj, existing); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// at this point we have a fully formed object. It is time to call the validators that the apiserver
|
||||
// handling chain wants to enforce.
|
||||
if updateValidation != nil {
|
||||
if err := updateValidation(obj.DeepCopyObject(), existing.DeepCopyObject()); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if e.shouldDeleteDuringUpdate(ctx, key, obj, existing) {
|
||||
deleteObj = obj
|
||||
return nil, nil, errEmptiedFinalizers
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ func TestStoreListResourceVersion(t *testing.T) {
|
|||
destroyFunc, registry := newTestGenericStoreRegistry(t, scheme, true)
|
||||
defer destroyFunc()
|
||||
|
||||
obj, err := registry.Create(ctx, fooPod, false)
|
||||
obj, err := registry.Create(ctx, fooPod, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -275,7 +275,7 @@ func TestStoreListResourceVersion(t *testing.T) {
|
|||
t.Fatalf("expected waiting, but get %#v", l)
|
||||
}
|
||||
|
||||
if _, err := registry.Create(ctx, barPod, false); err != nil {
|
||||
if _, err := registry.Create(ctx, barPod, rest.ValidateAllObjectFunc, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
@ -312,7 +312,7 @@ func TestStoreCreate(t *testing.T) {
|
|||
registry.DeleteStrategy = testGracefulStrategy{defaultDeleteStrategy}
|
||||
|
||||
// create the object
|
||||
objA, err := registry.Create(testContext, podA, false)
|
||||
objA, err := registry.Create(testContext, podA, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -329,7 +329,7 @@ func TestStoreCreate(t *testing.T) {
|
|||
}
|
||||
|
||||
// now try to create the second pod
|
||||
_, err = registry.Create(testContext, podB, false)
|
||||
_, err = registry.Create(testContext, podB, rest.ValidateAllObjectFunc, false)
|
||||
if !errors.IsAlreadyExists(err) {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -348,7 +348,7 @@ func TestStoreCreate(t *testing.T) {
|
|||
}
|
||||
|
||||
// try to create before graceful deletion period is over
|
||||
_, err = registry.Create(testContext, podA, false)
|
||||
_, err = registry.Create(testContext, podA, rest.ValidateAllObjectFunc, false)
|
||||
if err == nil || !errors.IsAlreadyExists(err) {
|
||||
t.Fatalf("Expected 'already exists' error from storage, but got %v", err)
|
||||
}
|
||||
|
|
@ -440,7 +440,7 @@ func TestStoreCreateInitialized(t *testing.T) {
|
|||
}
|
||||
|
||||
pod.Initializers = nil
|
||||
updated, _, err := registry.Update(ctx, podA.Name, rest.DefaultUpdatedObjectInfo(pod))
|
||||
updated, _, err := registry.Update(ctx, podA.Name, rest.DefaultUpdatedObjectInfo(pod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -476,7 +476,7 @@ func TestStoreCreateInitialized(t *testing.T) {
|
|||
}()
|
||||
|
||||
// create the object
|
||||
objA, err := registry.Create(ctx, podA, false)
|
||||
objA, err := registry.Create(ctx, podA, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -533,7 +533,7 @@ func TestStoreCreateInitializedFailed(t *testing.T) {
|
|||
}
|
||||
pod.Initializers.Pending = nil
|
||||
pod.Initializers.Result = &metav1.Status{Status: metav1.StatusFailure, Code: 403, Reason: metav1.StatusReasonForbidden, Message: "induced failure"}
|
||||
updated, _, err := registry.Update(ctx, podA.Name, rest.DefaultUpdatedObjectInfo(pod))
|
||||
updated, _, err := registry.Update(ctx, podA.Name, rest.DefaultUpdatedObjectInfo(pod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -556,7 +556,7 @@ func TestStoreCreateInitializedFailed(t *testing.T) {
|
|||
}()
|
||||
|
||||
// create the object
|
||||
_, err := registry.Create(ctx, podA, false)
|
||||
_, err := registry.Create(ctx, podA, rest.ValidateAllObjectFunc, false)
|
||||
if !errors.IsForbidden(err) {
|
||||
t.Fatalf("unexpected error: %#v", err.(errors.APIStatus).Status())
|
||||
}
|
||||
|
|
@ -574,7 +574,7 @@ func TestStoreCreateInitializedFailed(t *testing.T) {
|
|||
}
|
||||
|
||||
func updateAndVerify(t *testing.T, ctx genericapirequest.Context, registry *Store, pod *example.Pod) bool {
|
||||
obj, _, err := registry.Update(ctx, pod.Name, rest.DefaultUpdatedObjectInfo(pod))
|
||||
obj, _, err := registry.Update(ctx, pod.Name, rest.DefaultUpdatedObjectInfo(pod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return false
|
||||
|
|
@ -610,7 +610,7 @@ func TestStoreUpdate(t *testing.T) {
|
|||
defer destroyFunc()
|
||||
|
||||
// Test1 try to update a non-existing node
|
||||
_, _, err := registry.Update(testContext, podA.Name, rest.DefaultUpdatedObjectInfo(podA))
|
||||
_, _, err := registry.Update(testContext, podA.Name, rest.DefaultUpdatedObjectInfo(podA), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if !errors.IsNotFound(err) {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -623,7 +623,7 @@ func TestStoreUpdate(t *testing.T) {
|
|||
registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = false
|
||||
|
||||
// Test3 outofDate
|
||||
_, _, err = registry.Update(testContext, podAWithResourceVersion.Name, rest.DefaultUpdatedObjectInfo(podAWithResourceVersion))
|
||||
_, _, err = registry.Update(testContext, podAWithResourceVersion.Name, rest.DefaultUpdatedObjectInfo(podAWithResourceVersion), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if !errors.IsConflict(err) {
|
||||
t.Errorf("Unexpected error updating podAWithResourceVersion: %v", err)
|
||||
}
|
||||
|
|
@ -660,7 +660,7 @@ func TestNoOpUpdates(t *testing.T) {
|
|||
|
||||
var err error
|
||||
var createResult runtime.Object
|
||||
if createResult, err = registry.Create(genericapirequest.NewDefaultContext(), newPod(), false); err != nil {
|
||||
if createResult, err = registry.Create(genericapirequest.NewDefaultContext(), newPod(), rest.ValidateAllObjectFunc, false); err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
|
|
@ -671,7 +671,7 @@ func TestNoOpUpdates(t *testing.T) {
|
|||
|
||||
var updateResult runtime.Object
|
||||
p := newPod()
|
||||
if updateResult, _, err = registry.Update(genericapirequest.NewDefaultContext(), p.Name, rest.DefaultUpdatedObjectInfo(p)); err != nil {
|
||||
if updateResult, _, err = registry.Update(genericapirequest.NewDefaultContext(), p.Name, rest.DefaultUpdatedObjectInfo(p), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
|
|
@ -835,7 +835,7 @@ func TestStoreDelete(t *testing.T) {
|
|||
}
|
||||
|
||||
// create pod
|
||||
_, err = registry.Create(testContext, podA, false)
|
||||
_, err = registry.Create(testContext, podA, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -873,7 +873,7 @@ func TestStoreDeleteUninitialized(t *testing.T) {
|
|||
}
|
||||
|
||||
// create pod
|
||||
_, err = registry.Create(testContext, podA, true)
|
||||
_, err = registry.Create(testContext, podA, rest.ValidateAllObjectFunc, true)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -950,7 +950,7 @@ func TestGracefulStoreHandleFinalizers(t *testing.T) {
|
|||
registry.EnableGarbageCollection = gcEnabled
|
||||
|
||||
// create pod
|
||||
_, err := registry.Create(testContext, podWithFinalizer, false)
|
||||
_, err := registry.Create(testContext, podWithFinalizer, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -972,7 +972,7 @@ func TestGracefulStoreHandleFinalizers(t *testing.T) {
|
|||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Finalizers: []string{"foo.com/x"}, ResourceVersion: podWithFinalizer.ObjectMeta.ResourceVersion},
|
||||
Spec: example.PodSpec{NodeName: "machine"},
|
||||
}
|
||||
_, _, err = registry.Update(testContext, updatedPodWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(updatedPodWithFinalizer))
|
||||
_, _, err = registry.Update(testContext, updatedPodWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(updatedPodWithFinalizer), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -987,7 +987,7 @@ func TestGracefulStoreHandleFinalizers(t *testing.T) {
|
|||
ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: podWithFinalizer.ObjectMeta.ResourceVersion},
|
||||
Spec: example.PodSpec{NodeName: "anothermachine"},
|
||||
}
|
||||
_, _, err = registry.Update(testContext, podWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(podWithNoFinalizer))
|
||||
_, _, err = registry.Update(testContext, podWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(podWithNoFinalizer), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1016,7 +1016,7 @@ func TestFailedInitializationStoreUpdate(t *testing.T) {
|
|||
defer destroyFunc()
|
||||
|
||||
// create pod, view initializing
|
||||
obj, err := registry.Create(testContext, podInitializing, true)
|
||||
obj, err := registry.Create(testContext, podInitializing, rest.ValidateAllObjectFunc, true)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1024,7 +1024,7 @@ func TestFailedInitializationStoreUpdate(t *testing.T) {
|
|||
|
||||
// update the pod with initialization failure, the pod should be deleted
|
||||
pod.Initializers.Result = &metav1.Status{Status: metav1.StatusFailure}
|
||||
result, _, err := registry.Update(testContext, podInitializing.Name, rest.DefaultUpdatedObjectInfo(pod))
|
||||
result, _, err := registry.Update(testContext, podInitializing.Name, rest.DefaultUpdatedObjectInfo(pod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1055,7 +1055,7 @@ func TestNonGracefulStoreHandleFinalizers(t *testing.T) {
|
|||
registry.EnableGarbageCollection = gcEnabled
|
||||
|
||||
// create pod
|
||||
_, err := registry.Create(testContext, podWithFinalizer, false)
|
||||
_, err := registry.Create(testContext, podWithFinalizer, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1092,7 +1092,7 @@ func TestNonGracefulStoreHandleFinalizers(t *testing.T) {
|
|||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Finalizers: []string{"foo.com/x"}, ResourceVersion: podWithFinalizer.ObjectMeta.ResourceVersion},
|
||||
Spec: example.PodSpec{NodeName: "machine"},
|
||||
}
|
||||
_, _, err = registry.Update(testContext, updatedPodWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(updatedPodWithFinalizer))
|
||||
_, _, err = registry.Update(testContext, updatedPodWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(updatedPodWithFinalizer), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1111,7 +1111,7 @@ func TestNonGracefulStoreHandleFinalizers(t *testing.T) {
|
|||
ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: podWithFinalizer.ObjectMeta.ResourceVersion},
|
||||
Spec: example.PodSpec{NodeName: "anothermachine"},
|
||||
}
|
||||
_, _, err = registry.Update(testContext, podWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(podWithNoFinalizer))
|
||||
_, _, err = registry.Update(testContext, podWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(podWithNoFinalizer), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1357,7 +1357,7 @@ func TestStoreDeleteWithOrphanDependents(t *testing.T) {
|
|||
for _, tc := range testcases {
|
||||
registry.DeleteStrategy = tc.strategy
|
||||
// create pod
|
||||
_, err := registry.Create(testContext, tc.pod, false)
|
||||
_, err := registry.Create(testContext, tc.pod, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1576,7 +1576,7 @@ func TestStoreDeletionPropagation(t *testing.T) {
|
|||
i++
|
||||
pod := createPod(i, tc.existingFinalizers)
|
||||
// create pod
|
||||
_, err := registry.Create(testContext, pod, false)
|
||||
_, err := registry.Create(testContext, pod, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1628,13 +1628,13 @@ func TestStoreDeleteCollection(t *testing.T) {
|
|||
destroyFunc, registry := NewTestGenericStoreRegistry(t)
|
||||
defer destroyFunc()
|
||||
|
||||
if _, err := registry.Create(testContext, podA, false); err != nil {
|
||||
if _, err := registry.Create(testContext, podA, rest.ValidateAllObjectFunc, false); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if _, err := registry.Create(testContext, podB, false); err != nil {
|
||||
if _, err := registry.Create(testContext, podB, rest.ValidateAllObjectFunc, false); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if _, err := registry.Create(testContext, podC, true); err != nil {
|
||||
if _, err := registry.Create(testContext, podC, rest.ValidateAllObjectFunc, true); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
|
|
@ -1670,10 +1670,10 @@ func TestStoreDeleteCollectionNotFound(t *testing.T) {
|
|||
|
||||
for i := 0; i < 10; i++ {
|
||||
// Setup
|
||||
if _, err := registry.Create(testContext, podA, false); err != nil {
|
||||
if _, err := registry.Create(testContext, podA, rest.ValidateAllObjectFunc, false); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if _, err := registry.Create(testContext, podB, false); err != nil {
|
||||
if _, err := registry.Create(testContext, podB, rest.ValidateAllObjectFunc, false); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
|
|
@ -1709,7 +1709,7 @@ func TestStoreDeleteCollectionWithWatch(t *testing.T) {
|
|||
destroyFunc, registry := NewTestGenericStoreRegistry(t)
|
||||
defer destroyFunc()
|
||||
|
||||
objCreated, err := registry.Create(testContext, podA, false)
|
||||
objCreated, err := registry.Create(testContext, podA, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1778,7 +1778,7 @@ func TestStoreWatch(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Errorf("%v: unexpected error: %v", name, err)
|
||||
} else {
|
||||
obj, err := registry.Create(testContext, podA, false)
|
||||
obj, err := registry.Create(testContext, podA, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
got, open := <-wi.ResultChan()
|
||||
if !open {
|
||||
|
|
@ -1922,7 +1922,7 @@ func TestQualifiedResource(t *testing.T) {
|
|||
defer destroyFunc()
|
||||
|
||||
// update a non-exist object
|
||||
_, _, err := registry.Update(testContext, podA.Name, rest.DefaultUpdatedObjectInfo(podA))
|
||||
_, _, err := registry.Update(testContext, podA.Name, rest.DefaultUpdatedObjectInfo(podA), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if !errors.IsNotFound(err) {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1954,13 +1954,13 @@ func TestQualifiedResource(t *testing.T) {
|
|||
}
|
||||
|
||||
// create a non-exist object
|
||||
_, err = registry.Create(testContext, podA, false)
|
||||
_, err = registry.Create(testContext, podA, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// create a exist object will fail
|
||||
_, err = registry.Create(testContext, podA, false)
|
||||
_, err = registry.Create(testContext, podA, rest.ValidateAllObjectFunc, false)
|
||||
if !errors.IsAlreadyExists(err) {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ go_library(
|
|||
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/features:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
|
|
@ -151,3 +152,28 @@ type NamespaceScopedStrategy interface {
|
|||
// NamespaceScoped returns if the object must be in a namespace.
|
||||
NamespaceScoped() bool
|
||||
}
|
||||
|
||||
// AdmissionToValidateObjectFunc converts validating admission to a rest validate object func
|
||||
func AdmissionToValidateObjectFunc(admit admission.Interface, staticAttributes admission.Attributes) ValidateObjectFunc {
|
||||
validatingAdmission, ok := admit.(admission.ValidationInterface)
|
||||
if !ok {
|
||||
return func(obj runtime.Object) error { return nil }
|
||||
}
|
||||
return func(obj runtime.Object) error {
|
||||
finalAttributes := admission.NewAttributesRecord(
|
||||
obj,
|
||||
staticAttributes.GetOldObject(),
|
||||
staticAttributes.GetKind(),
|
||||
staticAttributes.GetNamespace(),
|
||||
staticAttributes.GetName(),
|
||||
staticAttributes.GetResource(),
|
||||
staticAttributes.GetSubresource(),
|
||||
staticAttributes.GetOperation(),
|
||||
staticAttributes.GetUserInfo(),
|
||||
)
|
||||
if !validatingAdmission.Handles(finalAttributes.GetOperation()) {
|
||||
return nil
|
||||
}
|
||||
return validatingAdmission.Validate(finalAttributes)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ type Creater interface {
|
|||
|
||||
// Create creates a new version of a resource. If includeUninitialized is set, the object may be returned
|
||||
// without completing initialization.
|
||||
Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error)
|
||||
Create(ctx genericapirequest.Context, obj runtime.Object, createValidation ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// NamedCreater is an object that can create an instance of a RESTful object using a name parameter.
|
||||
|
|
@ -205,7 +205,7 @@ type NamedCreater interface {
|
|||
// This is needed for create operations on subresources which include the name of the parent
|
||||
// resource in the path. If includeUninitialized is set, the object may be returned without
|
||||
// completing initialization.
|
||||
Create(ctx genericapirequest.Context, name string, obj runtime.Object, includeUninitialized bool) (runtime.Object, error)
|
||||
Create(ctx genericapirequest.Context, name string, obj runtime.Object, createValidation ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// UpdatedObjectInfo provides information about an updated object to an Updater.
|
||||
|
|
@ -221,6 +221,26 @@ type UpdatedObjectInfo interface {
|
|||
UpdatedObject(ctx genericapirequest.Context, oldObj runtime.Object) (newObj runtime.Object, err error)
|
||||
}
|
||||
|
||||
// ValidateObjectFunc is a function to act on a given object. An error may be returned
|
||||
// if the hook cannot be completed. An ObjectFunc may NOT transform the provided
|
||||
// object.
|
||||
type ValidateObjectFunc func(obj runtime.Object) error
|
||||
|
||||
// ValidateAllObjectFunc is a "admit everything" instance of ValidateObjectFunc.
|
||||
func ValidateAllObjectFunc(obj runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateObjectUpdateFunc is a function to act on a given object and its predecessor.
|
||||
// An error may be returned if the hook cannot be completed. An UpdateObjectFunc
|
||||
// may NOT transform the provided object.
|
||||
type ValidateObjectUpdateFunc func(obj, old runtime.Object) error
|
||||
|
||||
// ValidateAllObjectUpdateFunc is a "admit everything" instance of ValidateObjectUpdateFunc.
|
||||
func ValidateAllObjectUpdateFunc(obj, old runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Updater is an object that can update an instance of a RESTful object.
|
||||
type Updater interface {
|
||||
// New returns an empty object that can be used with Update after request data has been put into it.
|
||||
|
|
@ -230,14 +250,14 @@ type Updater interface {
|
|||
// Update finds a resource in the storage and updates it. Some implementations
|
||||
// may allow updates creates the object - they should set the created boolean
|
||||
// to true.
|
||||
Update(ctx genericapirequest.Context, name string, objInfo UpdatedObjectInfo) (runtime.Object, bool, error)
|
||||
Update(ctx genericapirequest.Context, name string, objInfo UpdatedObjectInfo, createValidation ValidateObjectFunc, updateValidation ValidateObjectUpdateFunc) (runtime.Object, bool, error)
|
||||
}
|
||||
|
||||
// CreaterUpdater is a storage object that must support both create and update.
|
||||
// Go prevents embedded interfaces that implement the same method.
|
||||
type CreaterUpdater interface {
|
||||
Creater
|
||||
Update(ctx genericapirequest.Context, name string, objInfo UpdatedObjectInfo) (runtime.Object, bool, error)
|
||||
Update(ctx genericapirequest.Context, name string, objInfo UpdatedObjectInfo, createValidation ValidateObjectFunc, updateValidation ValidateObjectUpdateFunc) (runtime.Object, bool, error)
|
||||
}
|
||||
|
||||
// CreaterUpdater must satisfy the Updater interface.
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ func (t *Tester) testCreateAlreadyExisting(obj runtime.Object, createFn CreateFu
|
|||
}
|
||||
defer t.delete(ctx, foo)
|
||||
|
||||
_, err := t.storage.(rest.Creater).Create(ctx, foo, false)
|
||||
_, err := t.storage.(rest.Creater).Create(ctx, foo, rest.ValidateAllObjectFunc, false)
|
||||
if !errors.IsAlreadyExists(err) {
|
||||
t.Errorf("expected already exists err, got %v", err)
|
||||
}
|
||||
|
|
@ -256,7 +256,7 @@ func (t *Tester) testCreateEquals(obj runtime.Object, getFn GetFunc) {
|
|||
foo := obj.DeepCopyObject()
|
||||
t.setObjectMeta(foo, t.namer(2))
|
||||
|
||||
created, err := t.storage.(rest.Creater).Create(ctx, foo, false)
|
||||
created, err := t.storage.(rest.Creater).Create(ctx, foo, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -284,7 +284,7 @@ func (t *Tester) testCreateDiscardsObjectNamespace(valid runtime.Object) {
|
|||
objectMeta.SetNamespace("not-default")
|
||||
|
||||
// Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted
|
||||
created, err := t.storage.(rest.Creater).Create(t.TestContext(), valid.DeepCopyObject(), false)
|
||||
created, err := t.storage.(rest.Creater).Create(t.TestContext(), valid.DeepCopyObject(), rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -300,7 +300,7 @@ func (t *Tester) testCreateGeneratesName(valid runtime.Object) {
|
|||
objectMeta.SetName("")
|
||||
objectMeta.SetGenerateName("test-")
|
||||
|
||||
created, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, false)
|
||||
created, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -315,7 +315,7 @@ func (t *Tester) testCreateHasMetadata(valid runtime.Object) {
|
|||
objectMeta.SetName(t.namer(1))
|
||||
objectMeta.SetNamespace(t.TestNamespace())
|
||||
|
||||
obj, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, false)
|
||||
obj, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -333,7 +333,7 @@ func (t *Tester) testCreateIgnoresContextNamespace(valid runtime.Object) {
|
|||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "not-default2")
|
||||
|
||||
// Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted
|
||||
created, err := t.storage.(rest.Creater).Create(ctx, valid.DeepCopyObject(), false)
|
||||
created, err := t.storage.(rest.Creater).Create(ctx, valid.DeepCopyObject(), rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -352,7 +352,7 @@ func (t *Tester) testCreateIgnoresMismatchedNamespace(valid runtime.Object) {
|
|||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "not-default2")
|
||||
|
||||
// Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted
|
||||
created, err := t.storage.(rest.Creater).Create(ctx, valid.DeepCopyObject(), false)
|
||||
created, err := t.storage.(rest.Creater).Create(ctx, valid.DeepCopyObject(), rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -370,7 +370,7 @@ func (t *Tester) testCreateValidatesNames(valid runtime.Object) {
|
|||
objCopyMeta.SetName(invalidName)
|
||||
|
||||
ctx := t.TestContext()
|
||||
_, err := t.storage.(rest.Creater).Create(ctx, objCopy, false)
|
||||
_, err := t.storage.(rest.Creater).Create(ctx, objCopy, rest.ValidateAllObjectFunc, false)
|
||||
if !errors.IsInvalid(err) {
|
||||
t.Errorf("%s: Expected to get an invalid resource error, got '%v'", invalidName, err)
|
||||
}
|
||||
|
|
@ -382,7 +382,7 @@ func (t *Tester) testCreateValidatesNames(valid runtime.Object) {
|
|||
objCopyMeta.SetName(objCopyMeta.GetName() + invalidSuffix)
|
||||
|
||||
ctx := t.TestContext()
|
||||
_, err := t.storage.(rest.Creater).Create(ctx, objCopy, false)
|
||||
_, err := t.storage.(rest.Creater).Create(ctx, objCopy, rest.ValidateAllObjectFunc, false)
|
||||
if !errors.IsInvalid(err) {
|
||||
t.Errorf("%s: Expected to get an invalid resource error, got '%v'", invalidSuffix, err)
|
||||
}
|
||||
|
|
@ -392,7 +392,7 @@ func (t *Tester) testCreateValidatesNames(valid runtime.Object) {
|
|||
func (t *Tester) testCreateInvokesValidation(invalid ...runtime.Object) {
|
||||
for i, obj := range invalid {
|
||||
ctx := t.TestContext()
|
||||
_, err := t.storage.(rest.Creater).Create(ctx, obj, false)
|
||||
_, err := t.storage.(rest.Creater).Create(ctx, obj, rest.ValidateAllObjectFunc, false)
|
||||
if !errors.IsInvalid(err) {
|
||||
t.Errorf("%d: Expected to get an invalid resource error, got %v", i, err)
|
||||
}
|
||||
|
|
@ -403,7 +403,7 @@ func (t *Tester) testCreateRejectsMismatchedNamespace(valid runtime.Object) {
|
|||
objectMeta := t.getObjectMetaOrFail(valid)
|
||||
objectMeta.SetNamespace("not-default")
|
||||
|
||||
_, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, false)
|
||||
_, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, rest.ValidateAllObjectFunc, false)
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error, but we didn't get one")
|
||||
} else if !strings.Contains(err.Error(), "does not match the namespace sent on the request") {
|
||||
|
|
@ -417,7 +417,7 @@ func (t *Tester) testCreateResetsUserData(valid runtime.Object) {
|
|||
objectMeta.SetUID("bad-uid")
|
||||
objectMeta.SetCreationTimestamp(now)
|
||||
|
||||
obj, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, false)
|
||||
obj, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -435,7 +435,7 @@ func (t *Tester) testCreateIgnoreClusterName(valid runtime.Object) {
|
|||
objectMeta.SetName(t.namer(3))
|
||||
objectMeta.SetClusterName("clustername-to-ignore")
|
||||
|
||||
obj, err := t.storage.(rest.Creater).Create(t.TestContext(), valid.DeepCopyObject(), false)
|
||||
obj, err := t.storage.(rest.Creater).Create(t.TestContext(), valid.DeepCopyObject(), rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -464,7 +464,7 @@ func (t *Tester) testUpdateEquals(obj runtime.Object, createFn CreateFunc, getFn
|
|||
}
|
||||
toUpdate = updateFn(toUpdate)
|
||||
toUpdateMeta := t.getObjectMetaOrFail(toUpdate)
|
||||
updated, created, err := t.storage.(rest.Updater).Update(ctx, toUpdateMeta.GetName(), rest.DefaultUpdatedObjectInfo(toUpdate))
|
||||
updated, created, err := t.storage.(rest.Updater).Update(ctx, toUpdateMeta.GetName(), rest.DefaultUpdatedObjectInfo(toUpdate), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -504,7 +504,7 @@ func (t *Tester) testUpdateFailsOnVersionTooOld(obj runtime.Object, createFn Cre
|
|||
olderMeta := t.getObjectMetaOrFail(older)
|
||||
olderMeta.SetResourceVersion("1")
|
||||
|
||||
_, _, err = t.storage.(rest.Updater).Update(t.TestContext(), olderMeta.GetName(), rest.DefaultUpdatedObjectInfo(older))
|
||||
_, _, err = t.storage.(rest.Updater).Update(t.TestContext(), olderMeta.GetName(), rest.DefaultUpdatedObjectInfo(older), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error, but we didn't get one")
|
||||
} else if !errors.IsConflict(err) {
|
||||
|
|
@ -524,7 +524,7 @@ func (t *Tester) testUpdateInvokesValidation(obj runtime.Object, createFn Create
|
|||
for _, update := range invalidUpdateFn {
|
||||
toUpdate := update(foo.DeepCopyObject())
|
||||
toUpdateMeta := t.getObjectMetaOrFail(toUpdate)
|
||||
got, created, err := t.storage.(rest.Updater).Update(t.TestContext(), toUpdateMeta.GetName(), rest.DefaultUpdatedObjectInfo(toUpdate))
|
||||
got, created, err := t.storage.(rest.Updater).Update(t.TestContext(), toUpdateMeta.GetName(), rest.DefaultUpdatedObjectInfo(toUpdate), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if got != nil || created {
|
||||
t.Errorf("expected nil object and no creation for object: %v", toUpdate)
|
||||
}
|
||||
|
|
@ -545,7 +545,7 @@ func (t *Tester) testUpdateWithWrongUID(obj runtime.Object, createFn CreateFunc,
|
|||
}
|
||||
objectMeta.SetUID(types.UID("UID1111"))
|
||||
|
||||
obj, created, err := t.storage.(rest.Updater).Update(ctx, objectMeta.GetName(), rest.DefaultUpdatedObjectInfo(foo))
|
||||
obj, created, err := t.storage.(rest.Updater).Update(ctx, objectMeta.GetName(), rest.DefaultUpdatedObjectInfo(foo), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if created || obj != nil {
|
||||
t.Errorf("expected nil object and no creation for object: %v", foo)
|
||||
}
|
||||
|
|
@ -589,7 +589,7 @@ func (t *Tester) testUpdateRetrievesOldObject(obj runtime.Object, createFn Creat
|
|||
return updatedObject, nil
|
||||
}
|
||||
|
||||
updatedObj, created, err := t.storage.(rest.Updater).Update(ctx, objectMeta.GetName(), rest.DefaultUpdatedObjectInfo(storedFooWithUpdates, noopTransform))
|
||||
updatedObj, created, err := t.storage.(rest.Updater).Update(ctx, objectMeta.GetName(), rest.DefaultUpdatedObjectInfo(storedFooWithUpdates, noopTransform), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
|
|
@ -624,7 +624,7 @@ func (t *Tester) testUpdatePropagatesUpdatedObjectError(obj runtime.Object, crea
|
|||
return nil, propagateErr
|
||||
}
|
||||
|
||||
_, _, err := t.storage.(rest.Updater).Update(ctx, name, rest.DefaultUpdatedObjectInfo(foo, noopTransform))
|
||||
_, _, err := t.storage.(rest.Updater).Update(ctx, name, rest.DefaultUpdatedObjectInfo(foo, noopTransform), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err != propagateErr {
|
||||
t.Errorf("expected propagated error, got %#v", err)
|
||||
}
|
||||
|
|
@ -650,7 +650,7 @@ func (t *Tester) testUpdateIgnoreGenerationUpdates(obj runtime.Object, createFn
|
|||
olderMeta := t.getObjectMetaOrFail(older)
|
||||
olderMeta.SetGeneration(2)
|
||||
|
||||
_, _, err = t.storage.(rest.Updater).Update(t.TestContext(), olderMeta.GetName(), rest.DefaultUpdatedObjectInfo(older))
|
||||
_, _, err = t.storage.(rest.Updater).Update(t.TestContext(), olderMeta.GetName(), rest.DefaultUpdatedObjectInfo(older), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -666,7 +666,7 @@ func (t *Tester) testUpdateIgnoreGenerationUpdates(obj runtime.Object, createFn
|
|||
|
||||
func (t *Tester) testUpdateOnNotFound(obj runtime.Object) {
|
||||
t.setObjectMeta(obj, t.namer(0))
|
||||
_, created, err := t.storage.(rest.Updater).Update(t.TestContext(), t.namer(0), rest.DefaultUpdatedObjectInfo(obj))
|
||||
_, created, err := t.storage.(rest.Updater).Update(t.TestContext(), t.namer(0), rest.DefaultUpdatedObjectInfo(obj), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if t.createOnUpdate {
|
||||
if err != nil {
|
||||
t.Errorf("creation allowed on updated, but got an error: %v", err)
|
||||
|
|
@ -701,7 +701,7 @@ func (t *Tester) testUpdateRejectsMismatchedNamespace(obj runtime.Object, create
|
|||
objectMeta.SetName(t.namer(1))
|
||||
objectMeta.SetNamespace("not-default")
|
||||
|
||||
obj, updated, err := t.storage.(rest.Updater).Update(t.TestContext(), "foo1", rest.DefaultUpdatedObjectInfo(storedFoo))
|
||||
obj, updated, err := t.storage.(rest.Updater).Update(t.TestContext(), "foo1", rest.DefaultUpdatedObjectInfo(storedFoo), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if obj != nil || updated {
|
||||
t.Errorf("expected nil object and not updated")
|
||||
}
|
||||
|
|
@ -732,7 +732,7 @@ func (t *Tester) testUpdateIgnoreClusterName(obj runtime.Object, createFn Create
|
|||
olderMeta := t.getObjectMetaOrFail(older)
|
||||
olderMeta.SetClusterName("clustername-to-ignore")
|
||||
|
||||
_, _, err = t.storage.(rest.Updater).Update(t.TestContext(), olderMeta.GetName(), rest.DefaultUpdatedObjectInfo(older))
|
||||
_, _, err = t.storage.(rest.Updater).Update(t.TestContext(), olderMeta.GetName(), rest.DefaultUpdatedObjectInfo(older), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1064,14 +1064,14 @@ func (t *Tester) testGetDifferentNamespace(obj runtime.Object) {
|
|||
|
||||
ctx1 := genericapirequest.WithNamespace(genericapirequest.NewContext(), "bar3")
|
||||
objMeta.SetNamespace(genericapirequest.NamespaceValue(ctx1))
|
||||
_, err := t.storage.(rest.Creater).Create(ctx1, obj, false)
|
||||
_, err := t.storage.(rest.Creater).Create(ctx1, obj, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
ctx2 := genericapirequest.WithNamespace(genericapirequest.NewContext(), "bar4")
|
||||
objMeta.SetNamespace(genericapirequest.NamespaceValue(ctx2))
|
||||
_, err = t.storage.(rest.Creater).Create(ctx2, obj, false)
|
||||
_, err = t.storage.(rest.Creater).Create(ctx2, obj, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1105,7 +1105,7 @@ func (t *Tester) testGetFound(obj runtime.Object) {
|
|||
ctx := t.TestContext()
|
||||
t.setObjectMeta(obj, t.namer(1))
|
||||
|
||||
existing, err := t.storage.(rest.Creater).Create(ctx, obj, false)
|
||||
existing, err := t.storage.(rest.Creater).Create(ctx, obj, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1128,7 +1128,7 @@ func (t *Tester) testGetMimatchedNamespace(obj runtime.Object) {
|
|||
objMeta := t.getObjectMetaOrFail(obj)
|
||||
objMeta.SetName(t.namer(4))
|
||||
objMeta.SetNamespace(genericapirequest.NamespaceValue(ctx1))
|
||||
_, err := t.storage.(rest.Creater).Create(ctx1, obj, false)
|
||||
_, err := t.storage.(rest.Creater).Create(ctx1, obj, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -1147,7 +1147,7 @@ func (t *Tester) testGetMimatchedNamespace(obj runtime.Object) {
|
|||
func (t *Tester) testGetNotFound(obj runtime.Object) {
|
||||
ctx := t.TestContext()
|
||||
t.setObjectMeta(obj, t.namer(2))
|
||||
_, err := t.storage.(rest.Creater).Create(ctx, obj, false)
|
||||
_, err := t.storage.(rest.Creater).Create(ctx, obj, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
|
|
@ -229,3 +230,28 @@ func (i *wrappedUpdatedObjectInfo) UpdatedObject(ctx genericapirequest.Context,
|
|||
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// AdmissionToValidateObjectUpdateFunc converts validating admission to a rest validate object update func
|
||||
func AdmissionToValidateObjectUpdateFunc(admit admission.Interface, staticAttributes admission.Attributes) ValidateObjectUpdateFunc {
|
||||
validatingAdmission, ok := admit.(admission.ValidationInterface)
|
||||
if !ok {
|
||||
return func(obj, old runtime.Object) error { return nil }
|
||||
}
|
||||
return func(obj, old runtime.Object) error {
|
||||
finalAttributes := admission.NewAttributesRecord(
|
||||
obj,
|
||||
old,
|
||||
staticAttributes.GetKind(),
|
||||
staticAttributes.GetNamespace(),
|
||||
staticAttributes.GetName(),
|
||||
staticAttributes.GetResource(),
|
||||
staticAttributes.GetSubresource(),
|
||||
staticAttributes.GetOperation(),
|
||||
staticAttributes.GetUserInfo(),
|
||||
)
|
||||
if !validatingAdmission.Handles(finalAttributes.GetOperation()) {
|
||||
return nil
|
||||
}
|
||||
return validatingAdmission.Validate(finalAttributes)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue