diff --git a/staging/src/k8s.io/sample-apiserver/pkg/admission/plugin/banflunder/BUILD b/staging/src/k8s.io/sample-apiserver/pkg/admission/plugin/banflunder/BUILD new file mode 100644 index 00000000000..626fd3bdd8f --- /dev/null +++ b/staging/src/k8s.io/sample-apiserver/pkg/admission/plugin/banflunder/BUILD @@ -0,0 +1,42 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["admission.go"], + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/sample-apiserver/pkg/admission/wardleinitializer:go_default_library", + "//vendor/k8s.io/sample-apiserver/pkg/apis/wardle:go_default_library", + "//vendor/k8s.io/sample-apiserver/pkg/client/informers_generated/internalversion:go_default_library", + "//vendor/k8s.io/sample-apiserver/pkg/client/listers_generated/wardle/internalversion:go_default_library", + ], +) + +go_test( + name = "go_default_xtest", + srcs = ["admission_test.go"], + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + "//vendor/k8s.io/sample-apiserver/pkg/admission/plugin/banflunder:go_default_library", + "//vendor/k8s.io/sample-apiserver/pkg/admission/wardleinitializer:go_default_library", + "//vendor/k8s.io/sample-apiserver/pkg/apis/wardle:go_default_library", + "//vendor/k8s.io/sample-apiserver/pkg/client/clientset_generated/internalclientset/fake:go_default_library", + "//vendor/k8s.io/sample-apiserver/pkg/client/informers_generated/internalversion:go_default_library", + ], +) diff --git a/staging/src/k8s.io/sample-apiserver/pkg/admission/plugin/banflunder/admission.go b/staging/src/k8s.io/sample-apiserver/pkg/admission/plugin/banflunder/admission.go new file mode 100644 index 00000000000..8e021f120f2 --- /dev/null +++ b/staging/src/k8s.io/sample-apiserver/pkg/admission/plugin/banflunder/admission.go @@ -0,0 +1,84 @@ +/* +Copyright 2017 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 banflunder + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apiserver/pkg/admission" + "k8s.io/sample-apiserver/pkg/admission/wardleinitializer" + "k8s.io/sample-apiserver/pkg/apis/wardle" + informers "k8s.io/sample-apiserver/pkg/client/informers_generated/internalversion" + listers "k8s.io/sample-apiserver/pkg/client/listers_generated/wardle/internalversion" +) + +type disallowFlunder struct { + *admission.Handler + lister listers.FischerLister +} + +var _ = wardleinitializer.WantsInternalWardleInformerFactory(&disallowFlunder{}) + +// Admit ensures that the object in-flight is of kind Flunder. +// In addition checks that the Name is not on the banned list. +// The list is stored in Fischers API objects. +func (d *disallowFlunder) Admit(a admission.Attributes) error { + // we are only interested in flunders + if a.GetKind().GroupKind() != wardle.Kind("Flunder") { + return nil + } + + flunderName := a.GetName() + fischers, err := d.lister.List(labels.Everything()) + if err != nil { + return err + } + + for _, fischer := range fischers { + for _, disallowedFlunder := range fischer.DisallowedFlunders { + if flunderName == disallowedFlunder { + return errors.NewForbidden( + a.GetResource().GroupResource(), + a.GetName(), + fmt.Errorf("this name may not be used, please change the resource name"), + ) + } + } + } + return nil +} + +// SetInternalWardleInformerFactory gets Lister from SharedInformerFactory. +// The lister knows how to lists Fischers. +func (d *disallowFlunder) SetInternalWardleInformerFactory(f informers.SharedInformerFactory) { + d.lister = f.Wardle().InternalVersion().Fischers().Lister() +} + +// Validate checks whether the plugin was correctly initialized. +func (d *disallowFlunder) Validate() error { + if d.lister == nil { + return fmt.Errorf("missing fischer lister") + } + return nil +} + +// New creates a new ban flunder admission plugin +func New() (admission.Interface, error) { + return &disallowFlunder{}, nil +} diff --git a/staging/src/k8s.io/sample-apiserver/pkg/admission/plugin/banflunder/admission_test.go b/staging/src/k8s.io/sample-apiserver/pkg/admission/plugin/banflunder/admission_test.go new file mode 100644 index 00000000000..e6c9e52d100 --- /dev/null +++ b/staging/src/k8s.io/sample-apiserver/pkg/admission/plugin/banflunder/admission_test.go @@ -0,0 +1,155 @@ +/* +Copyright 2017 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 banflunder_test + +import ( + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/admission" + clienttesting "k8s.io/client-go/testing" + "k8s.io/sample-apiserver/pkg/admission/plugin/banflunder" + "k8s.io/sample-apiserver/pkg/admission/wardleinitializer" + "k8s.io/sample-apiserver/pkg/apis/wardle" + "k8s.io/sample-apiserver/pkg/client/clientset_generated/internalclientset/fake" + informers "k8s.io/sample-apiserver/pkg/client/informers_generated/internalversion" +) + +// TestBanfluderAdmissionPlugin tests various test cases against +// ban flunder admission plugin +func TestBanflunderAdmissionPlugin(t *testing.T) { + var scenarios = []struct { + informersOutput wardle.FischerList + admissionInput wardle.Flunder + admissionInputKind schema.GroupVersionKind + admissionInputResource schema.GroupVersionResource + admissionMustFail bool + }{ + // scenario 1: + // a flunder with a name that appears on a list of disallowed flunders must be banned + { + informersOutput: wardle.FischerList{ + Items: []wardle.Fischer{ + {DisallowedFlunders: []string{"badname"}}, + }, + }, + admissionInput: wardle.Flunder{ + ObjectMeta: metav1.ObjectMeta{ + Name: "badname", + Namespace: "", + }, + }, + admissionInputKind: wardle.Kind("Flunder").WithVersion("version"), + admissionInputResource: wardle.Resource("flunders").WithVersion("version"), + admissionMustFail: true, + }, + // scenario 2: + // a flunder with a name that does not appear on a list of disallowed flunders must be admitted + { + informersOutput: wardle.FischerList{ + Items: []wardle.Fischer{ + {DisallowedFlunders: []string{"badname"}}, + }, + }, + admissionInput: wardle.Flunder{ + ObjectMeta: metav1.ObjectMeta{ + Name: "goodname", + Namespace: "", + }, + }, + admissionInputKind: wardle.Kind("Flunder").WithVersion("version"), + admissionInputResource: wardle.Resource("flunders").WithVersion("version"), + admissionMustFail: false, + }, + // scenario 3: + // a flunder with a name that appears on a list of disallowed flunders would be banned + // but the kind passed in is not a flunder thus the whole request is accepted + { + informersOutput: wardle.FischerList{ + Items: []wardle.Fischer{ + {DisallowedFlunders: []string{"badname"}}, + }, + }, + admissionInput: wardle.Flunder{ + ObjectMeta: metav1.ObjectMeta{ + Name: "badname", + Namespace: "", + }, + }, + admissionInputKind: wardle.Kind("NotFlunder").WithVersion("version"), + admissionInputResource: wardle.Resource("notflunders").WithVersion("version"), + admissionMustFail: false, + }, + } + + for index, scenario := range scenarios { + func() { + // prepare + cs := &fake.Clientset{} + cs.AddReactor("list", "fischers", func(action clienttesting.Action) (bool, runtime.Object, error) { + return true, &scenario.informersOutput, nil + }) + informersFactory := informers.NewSharedInformerFactory(cs, 5*time.Minute) + + target, err := banflunder.New() + if err != nil { + t.Fatalf("scenario %d: failed to create banflunder admission plugin due to = %v", index, err) + } + + targetInitializer, err := wardleinitializer.New(informersFactory) + if err != nil { + t.Fatalf("scenario %d: failed to crate wardle plugin initializer due to = %v", index, err) + } + targetInitializer.Initialize(target) + + err = admission.Validate(target) + if err != nil { + t.Fatalf("scenario %d: failed to initialize banflunder admission plugin due to =%v", index, err) + } + + stop := make(chan struct{}) + defer close(stop) + informersFactory.Start(stop) + informersFactory.WaitForCacheSync(stop) + + // act + err = target.Admit(admission.NewAttributesRecord( + &scenario.admissionInput, + nil, + scenario.admissionInputKind, + scenario.admissionInput.ObjectMeta.Namespace, + scenario.admissionInput.ObjectMeta.Name, + scenario.admissionInputResource, + "", + admission.Create, + nil), + ) + + // validate + if scenario.admissionMustFail && err == nil { + t.Errorf("scenario %d: expected an error but got nothing", index) + } + + if !scenario.admissionMustFail && err != nil { + t.Errorf("scenario %d: banflunder admission plugin returned unexpected error = %v", index, err) + } + }() + } +}