move admission to genericapiserver
This commit is contained in:
parent
0c810c8cb6
commit
0243c13f2a
|
@ -0,0 +1,10 @@
|
||||||
|
approvers:
|
||||||
|
- davidopp
|
||||||
|
- derekwaynecarr
|
||||||
|
- erictune
|
||||||
|
- lavalamp
|
||||||
|
- liggitt
|
||||||
|
reviewers:
|
||||||
|
- deads2k
|
||||||
|
- ncdc
|
||||||
|
- smarterclayton
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
type attributesRecord struct {
|
||||||
|
kind schema.GroupVersionKind
|
||||||
|
namespace string
|
||||||
|
name string
|
||||||
|
resource schema.GroupVersionResource
|
||||||
|
subresource string
|
||||||
|
operation Operation
|
||||||
|
object runtime.Object
|
||||||
|
oldObject runtime.Object
|
||||||
|
userInfo user.Info
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind schema.GroupVersionKind, namespace, name string, resource schema.GroupVersionResource, subresource string, operation Operation, userInfo user.Info) Attributes {
|
||||||
|
return &attributesRecord{
|
||||||
|
kind: kind,
|
||||||
|
namespace: namespace,
|
||||||
|
name: name,
|
||||||
|
resource: resource,
|
||||||
|
subresource: subresource,
|
||||||
|
operation: operation,
|
||||||
|
object: object,
|
||||||
|
oldObject: oldObject,
|
||||||
|
userInfo: userInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetKind() schema.GroupVersionKind {
|
||||||
|
return record.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetNamespace() string {
|
||||||
|
return record.namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetName() string {
|
||||||
|
return record.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetResource() schema.GroupVersionResource {
|
||||||
|
return record.resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetSubresource() string {
|
||||||
|
return record.subresource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetOperation() Operation {
|
||||||
|
return record.operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetObject() runtime.Object {
|
||||||
|
return record.object
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetOldObject() runtime.Object {
|
||||||
|
return record.oldObject
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) GetUserInfo() user.Info {
|
||||||
|
return record.userInfo
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package admission
|
||||||
|
|
||||||
|
// chainAdmissionHandler is an instance of admission.Interface that performs admission control using a chain of admission handlers
|
||||||
|
type chainAdmissionHandler []Interface
|
||||||
|
|
||||||
|
// NewChainHandler creates a new chain handler from an array of handlers. Used for testing.
|
||||||
|
func NewChainHandler(handlers ...Interface) Interface {
|
||||||
|
return chainAdmissionHandler(handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admit performs an admission control check using a chain of handlers, and returns immediately on first error
|
||||||
|
func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error {
|
||||||
|
for _, handler := range admissionHandler {
|
||||||
|
if !handler.Handles(a.GetOperation()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := handler.Admit(a)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles will return true if any of the handlers handles the given operation
|
||||||
|
func (admissionHandler chainAdmissionHandler) Handles(operation Operation) bool {
|
||||||
|
for _, handler := range admissionHandler {
|
||||||
|
if handler.Handles(operation) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FakeHandler struct {
|
||||||
|
*Handler
|
||||||
|
name string
|
||||||
|
admit bool
|
||||||
|
admitCalled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *FakeHandler) Admit(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,
|
||||||
|
admit: admit,
|
||||||
|
Handler: NewHandler(ops...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdmit(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
operation Operation
|
||||||
|
chain chainAdmissionHandler
|
||||||
|
accept bool
|
||||||
|
calls map[string]bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "all accept",
|
||||||
|
operation: Create,
|
||||||
|
chain: []Interface{
|
||||||
|
makeHandler("a", true, Update, Delete, Create),
|
||||||
|
makeHandler("b", true, Delete, Create),
|
||||||
|
makeHandler("c", true, Create),
|
||||||
|
},
|
||||||
|
calls: map[string]bool{"a": true, "b": true, "c": true},
|
||||||
|
accept: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ignore handler",
|
||||||
|
operation: Create,
|
||||||
|
chain: []Interface{
|
||||||
|
makeHandler("a", true, Update, Delete, Create),
|
||||||
|
makeHandler("b", false, Delete),
|
||||||
|
makeHandler("c", true, Create),
|
||||||
|
},
|
||||||
|
calls: map[string]bool{"a": true, "c": true},
|
||||||
|
accept: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ignore all",
|
||||||
|
operation: Connect,
|
||||||
|
chain: []Interface{
|
||||||
|
makeHandler("a", true, Update, Delete, Create),
|
||||||
|
makeHandler("b", false, Delete),
|
||||||
|
makeHandler("c", true, Create),
|
||||||
|
},
|
||||||
|
calls: map[string]bool{},
|
||||||
|
accept: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reject one",
|
||||||
|
operation: Delete,
|
||||||
|
chain: []Interface{
|
||||||
|
makeHandler("a", true, Update, Delete, Create),
|
||||||
|
makeHandler("b", false, Delete),
|
||||||
|
makeHandler("c", true, Create),
|
||||||
|
},
|
||||||
|
calls: map[string]bool{"a": true, "b": true},
|
||||||
|
accept: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
err := test.chain.Admit(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "", schema.GroupVersionResource{}, "", test.operation, nil))
|
||||||
|
accepted := (err == nil)
|
||||||
|
if accepted != test.accept {
|
||||||
|
t.Errorf("%s: unexpected result of admit call: %v\n", test.name, accepted)
|
||||||
|
}
|
||||||
|
for _, h := range test.chain {
|
||||||
|
fake := h.(*FakeHandler)
|
||||||
|
_, shouldBeCalled := test.calls[fake.name]
|
||||||
|
if shouldBeCalled != fake.admitCalled {
|
||||||
|
t.Errorf("%s: handler %s not called as expected: %v", test.name, fake.name, fake.admitCalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandles(t *testing.T) {
|
||||||
|
chain := chainAdmissionHandler{
|
||||||
|
makeHandler("a", true, Update, Delete, Create),
|
||||||
|
makeHandler("b", true, Delete, Create),
|
||||||
|
makeHandler("c", true, Create),
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
operation Operation
|
||||||
|
chain chainAdmissionHandler
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "all handle",
|
||||||
|
operation: Create,
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "none handle",
|
||||||
|
operation: Connect,
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "some handle",
|
||||||
|
operation: Delete,
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
handles := chain.Handles(test.operation)
|
||||||
|
if handles != test.expected {
|
||||||
|
t.Errorf("Unexpected handles result. Expected: %v. Actual: %v", test.expected, handles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func extractResourceName(a Attributes) (name string, resource schema.GroupResource, err error) {
|
||||||
|
name = "Unknown"
|
||||||
|
resource = a.GetResource().GroupResource()
|
||||||
|
obj := a.GetObject()
|
||||||
|
if obj != nil {
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
return "", schema.GroupResource{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is necessary because name object name generation has not occurred yet
|
||||||
|
if len(accessor.GetName()) > 0 {
|
||||||
|
name = accessor.GetName()
|
||||||
|
} else if len(accessor.GetGenerateName()) > 0 {
|
||||||
|
name = accessor.GetGenerateName()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name, resource, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewForbidden is a utility function to return a well-formatted admission control error response
|
||||||
|
func NewForbidden(a Attributes, internalError error) error {
|
||||||
|
// do not double wrap an error of same type
|
||||||
|
if apierrors.IsForbidden(internalError) {
|
||||||
|
return internalError
|
||||||
|
}
|
||||||
|
name, resource, err := extractResourceName(a)
|
||||||
|
if err != nil {
|
||||||
|
return apierrors.NewInternalError(utilerrors.NewAggregate([]error{internalError, err}))
|
||||||
|
}
|
||||||
|
return apierrors.NewForbidden(resource, name, internalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotFound is a utility function to return a well-formatted admission control error response
|
||||||
|
func NewNotFound(a Attributes) error {
|
||||||
|
name, resource, err := extractResourceName(a)
|
||||||
|
if err != nil {
|
||||||
|
return apierrors.NewInternalError(err)
|
||||||
|
}
|
||||||
|
return apierrors.NewNotFound(resource, name)
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// timeToWaitForReady is the amount of time to wait to let an admission controller to be ready to satisfy a request.
|
||||||
|
// this is useful when admission controllers need to warm their caches before letting requests through.
|
||||||
|
timeToWaitForReady = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadyFunc is a function that returns true if the admission controller is ready to handle requests.
|
||||||
|
type ReadyFunc func() bool
|
||||||
|
|
||||||
|
// Handler is a base for admission control handlers that
|
||||||
|
// support a predefined set of operations
|
||||||
|
type Handler struct {
|
||||||
|
operations sets.String
|
||||||
|
readyFunc ReadyFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles returns true for methods that this handler supports
|
||||||
|
func (h *Handler) Handles(operation Operation) bool {
|
||||||
|
return h.operations.Has(string(operation))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler creates a new base handler that handles the passed
|
||||||
|
// in operations
|
||||||
|
func NewHandler(ops ...Operation) *Handler {
|
||||||
|
operations := sets.NewString()
|
||||||
|
for _, op := range ops {
|
||||||
|
operations.Insert(string(op))
|
||||||
|
}
|
||||||
|
return &Handler{
|
||||||
|
operations: operations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadyFunc allows late registration of a ReadyFunc to know if the handler is ready to process requests.
|
||||||
|
func (h *Handler) SetReadyFunc(readyFunc ReadyFunc) {
|
||||||
|
h.readyFunc = readyFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForReady will wait for the readyFunc (if registered) to return ready, and in case of timeout, will return false.
|
||||||
|
func (h *Handler) WaitForReady() bool {
|
||||||
|
// there is no ready func configured, so we return immediately
|
||||||
|
if h.readyFunc == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return h.waitForReadyInternal(time.After(timeToWaitForReady))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) waitForReadyInternal(timeout <-chan time.Time) bool {
|
||||||
|
// there is no configured ready func, so return immediately
|
||||||
|
if h.readyFunc == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for !h.readyFunc() {
|
||||||
|
select {
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
case <-timeout:
|
||||||
|
return h.readyFunc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attributes is an interface used by AdmissionController to get information about a request
|
||||||
|
// that is used to make an admission decision.
|
||||||
|
type Attributes interface {
|
||||||
|
// GetName returns the name of the object as presented in the request. On a CREATE operation, the client
|
||||||
|
// may omit name and rely on the server to generate the name. If that is the case, this method will return
|
||||||
|
// the empty string
|
||||||
|
GetName() string
|
||||||
|
// GetNamespace is the namespace associated with the request (if any)
|
||||||
|
GetNamespace() string
|
||||||
|
// GetResource is the name of the resource being requested. This is not the kind. For example: pods
|
||||||
|
GetResource() schema.GroupVersionResource
|
||||||
|
// GetSubresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind.
|
||||||
|
// For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod"
|
||||||
|
// (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding".
|
||||||
|
GetSubresource() string
|
||||||
|
// GetOperation is the operation being performed
|
||||||
|
GetOperation() Operation
|
||||||
|
// GetObject is the object from the incoming request prior to default values being applied
|
||||||
|
GetObject() runtime.Object
|
||||||
|
// GetOldObject is the existing object. Only populated for UPDATE requests.
|
||||||
|
GetOldObject() runtime.Object
|
||||||
|
// GetKind is the type of object being manipulated. For example: Pod
|
||||||
|
GetKind() schema.GroupVersionKind
|
||||||
|
// GetUserInfo is information about the requesting user
|
||||||
|
GetUserInfo() user.Info
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operation is the type of resource operation being checked for admission control
|
||||||
|
type Operation string
|
||||||
|
|
||||||
|
// Operation constants
|
||||||
|
const (
|
||||||
|
Create Operation = "CREATE"
|
||||||
|
Update Operation = "UPDATE"
|
||||||
|
Delete Operation = "DELETE"
|
||||||
|
Connect Operation = "CONNECT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PluginInitializer is used for initialization of shareable resources between admission plugins.
|
||||||
|
// After initialization the resources have to be set separately
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigProvider provides a way to get configuration for an admission plugin based on its name
|
||||||
|
type ConfigProvider interface {
|
||||||
|
ConfigFor(pluginName string) (io.Reader, error)
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Factory is a function that returns an Interface for admission decisions.
|
||||||
|
// The config parameter provides an io.Reader handler to the factory in
|
||||||
|
// order to load specific configurations. If no configuration is provided
|
||||||
|
// the parameter is nil.
|
||||||
|
type Factory func(config io.Reader) (Interface, error)
|
||||||
|
|
||||||
|
// All registered admission options.
|
||||||
|
var (
|
||||||
|
pluginsMutex sync.Mutex
|
||||||
|
plugins = make(map[string]Factory)
|
||||||
|
|
||||||
|
// PluginEnabledFn checks whether a plugin is enabled. By default, if you ask about it, it's enabled.
|
||||||
|
PluginEnabledFn = func(name string, config io.Reader) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// PluginEnabledFunc is a function type that can provide an external check on whether an admission plugin may be enabled
|
||||||
|
type PluginEnabledFunc func(name string, config io.Reader) bool
|
||||||
|
|
||||||
|
// GetPlugins enumerates the names of all registered plugins.
|
||||||
|
func GetPlugins() []string {
|
||||||
|
pluginsMutex.Lock()
|
||||||
|
defer pluginsMutex.Unlock()
|
||||||
|
keys := []string{}
|
||||||
|
for k := range plugins {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterPlugin registers a plugin Factory by name. This
|
||||||
|
// is expected to happen during app startup.
|
||||||
|
func RegisterPlugin(name string, plugin Factory) {
|
||||||
|
pluginsMutex.Lock()
|
||||||
|
defer pluginsMutex.Unlock()
|
||||||
|
_, found := plugins[name]
|
||||||
|
if found {
|
||||||
|
glog.Fatalf("Admission plugin %q was registered twice", name)
|
||||||
|
}
|
||||||
|
glog.V(1).Infof("Registered admission plugin %q", name)
|
||||||
|
plugins[name] = plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPlugin creates an instance of the named plugin. It returns `false` if the
|
||||||
|
// the name is not known. The error is returned only when the named provider was
|
||||||
|
// known but failed to initialize. The config parameter specifies the io.Reader
|
||||||
|
// handler of the configuration file for the cloud provider, or nil for no configuration.
|
||||||
|
func getPlugin(name string, config io.Reader) (Interface, bool, error) {
|
||||||
|
pluginsMutex.Lock()
|
||||||
|
defer pluginsMutex.Unlock()
|
||||||
|
f, found := plugins[name]
|
||||||
|
if !found {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
config1, config2, err := splitStream(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, true, err
|
||||||
|
}
|
||||||
|
if !PluginEnabledFn(name, config1) {
|
||||||
|
return nil, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := f(config2)
|
||||||
|
return ret, true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitStream reads the stream bytes and constructs two copies of it.
|
||||||
|
func splitStream(config io.Reader) (io.Reader, io.Reader, error) {
|
||||||
|
if config == nil || reflect.ValueOf(config).IsNil() {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
configBytes, err := ioutil.ReadAll(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes.NewBuffer(configBytes), bytes.NewBuffer(configBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFromPlugins returns an admission.Interface that will enforce admission control decisions of all
|
||||||
|
// the given plugins.
|
||||||
|
func NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer) (Interface, error) {
|
||||||
|
plugins := []Interface{}
|
||||||
|
for _, pluginName := range pluginNames {
|
||||||
|
pluginConfig, err := configProvider.ConfigFor(pluginName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin, err := InitPlugin(pluginName, pluginConfig, pluginInitializer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if plugin != nil {
|
||||||
|
plugins = append(plugins, plugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chainAdmissionHandler(plugins), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitPlugin creates an instance of the named interface.
|
||||||
|
func InitPlugin(name string, config io.Reader, pluginInitializer PluginInitializer) (Interface, error) {
|
||||||
|
if name == "" {
|
||||||
|
glog.Info("No admission plugin specified.")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin, found, err := getPlugin(name, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Couldn't init admission plugin %q: %v", name, err)
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("Unknown admission plugin: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginInitializer.Initialize(plugin)
|
||||||
|
// ensure that plugins have been properly initialized
|
||||||
|
if err := Validate(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()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PluginInitializers []PluginInitializer
|
||||||
|
|
||||||
|
func (pp PluginInitializers) Initialize(plugin Interface) {
|
||||||
|
for _, p := range pp {
|
||||||
|
p.Initialize(plugin)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue