Merge pull request #49891 from p0lyn0mial/sample_server_admission_plugin

Automatic merge from submit-queue (batch tested with PRs 49990, 49997, 44278, 49936, 49891)

adds an admission plugin to the sample apiserver.

**What this PR does / why we need it**:
adds an admission plugin to the sample apiserver.
the admission plugin checks whether `Flunder.Name` is not on the banned list.
including a unit test with various test scenarios.

**Special notes for your reviewer**:
https://github.com/kubernetes/kubernetes/issues/47868

**Release note**:

```
NONE
```
This commit is contained in:
Kubernetes Submit Queue 2017-08-02 10:21:51 -07:00 committed by GitHub
commit 64a984bb62
3 changed files with 281 additions and 0 deletions

View File

@ -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",
],
)

View File

@ -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
}

View File

@ -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)
}
}()
}
}