Merge pull request #46808 from caesarxuchao/make-daniels-pr-dynamic

Automatic merge from submit-queue (batch tested with PRs 47204, 46808, 47432, 47400, 47099)

Make the generic webhook admission controller use the dynamic webhook config manager

Based on #46672 and #46388.

Only the last commit is unique.

* removed `SetWebhookSource` from the PluginInitializer
* implemented `SetExternalClientset` for the generic webhook admisson controller, initializing an ExternalWebhookConfigurationManager in the method.
This commit is contained in:
Kubernetes Submit Queue
2017-06-14 17:13:56 -07:00
committed by GitHub
11 changed files with 237 additions and 103 deletions

View File

@@ -14,7 +14,6 @@ go_test(
library = ":go_default_library", library = ":go_default_library",
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//pkg/apis/admissionregistration:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
], ],
@@ -25,7 +24,6 @@ go_library(
srcs = ["initializer.go"], srcs = ["initializer.go"],
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//pkg/apis/admissionregistration:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library", "//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library",

View File

@@ -10,12 +10,17 @@ load(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = ["initializer_manager_test.go"], srcs = [
"configuration_manager_test.go",
"initializer_manager_test.go",
],
library = ":go_default_library", library = ":go_default_library",
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//pkg/apis/admissionregistration/v1alpha1:go_default_library", "//pkg/apis/admissionregistration/v1alpha1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//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/util/wait:go_default_library",
], ],
) )

View File

@@ -26,8 +26,10 @@ import (
) )
const ( const (
defaultInterval = 1 * time.Second defaultInterval = 1 * time.Second
defaultFailureThreshold = 5 defaultFailureThreshold = 5
defaultBootstrapRetries = 5
defaultBootstrapGraceperiod = 5 * time.Second
) )
var ( var (
@@ -47,26 +49,43 @@ type poller struct {
// a function to consistently read the latest configuration // a function to consistently read the latest configuration
get getFunc get getFunc
// consistent read interval // consistent read interval
// read-only
interval time.Duration interval time.Duration
// if the number of consecutive read failure equals or exceeds the failureThreshold , the // if the number of consecutive read failure equals or exceeds the failureThreshold , the
// configuration is regarded as not ready. // configuration is regarded as not ready.
// read-only
failureThreshold int failureThreshold int
// number of consecutive failures so far. // number of consecutive failures so far.
failures int failures int
// If the poller has passed the bootstrap phase. The poller is considered
// bootstrapped either bootstrapGracePeriod after the first call of
// configuration(), or when setConfigurationAndReady() is called, whichever
// comes first.
bootstrapped bool
// configuration() retries bootstrapRetries times if poller is not bootstrapped
// read-only
bootstrapRetries int
// Grace period for bootstrapping
// read-only
bootstrapGracePeriod time.Duration
once sync.Once
// if the configuration is regarded as ready. // if the configuration is regarded as ready.
ready bool ready bool
mergedConfiguration runtime.Object mergedConfiguration runtime.Object
// lock much be hold when reading ready or mergedConfiguration lastErr error
lock sync.RWMutex // lock must be hold when reading/writing the data fields of poller.
lastErr error lock sync.RWMutex
} }
func newPoller(get getFunc) *poller { func newPoller(get getFunc) *poller {
return &poller{ p := poller{
get: get, get: get,
interval: defaultInterval, interval: defaultInterval,
failureThreshold: defaultFailureThreshold, failureThreshold: defaultFailureThreshold,
bootstrapRetries: defaultBootstrapRetries,
bootstrapGracePeriod: defaultBootstrapGraceperiod,
} }
return &p
} }
func (a *poller) lastError(err error) { func (a *poller) lastError(err error) {
@@ -81,21 +100,47 @@ func (a *poller) notReady() {
a.ready = false a.ready = false
} }
func (a *poller) bootstrapping() {
// bootstrapGracePeriod is read-only, so no lock is required
timer := time.NewTimer(a.bootstrapGracePeriod)
go func() {
<-timer.C
a.lock.Lock()
defer a.lock.Unlock()
a.bootstrapped = true
}()
}
// If the poller is not bootstrapped yet, the configuration() gets a few chances
// to retry. This hides transient failures during system startup.
func (a *poller) configuration() (runtime.Object, error) { func (a *poller) configuration() (runtime.Object, error) {
a.once.Do(a.bootstrapping)
a.lock.RLock() a.lock.RLock()
defer a.lock.RUnlock() defer a.lock.RUnlock()
if !a.ready { retries := 1
if a.lastErr != nil { if !a.bootstrapped {
return nil, a.lastErr retries = a.bootstrapRetries
}
return nil, ErrNotReady
} }
return a.mergedConfiguration, nil for count := 0; count < retries; count++ {
if count > 0 {
a.lock.RUnlock()
time.Sleep(a.interval)
a.lock.RLock()
}
if a.ready {
return a.mergedConfiguration, nil
}
}
if a.lastErr != nil {
return nil, a.lastErr
}
return nil, ErrNotReady
} }
func (a *poller) setConfigurationAndReady(value runtime.Object) { func (a *poller) setConfigurationAndReady(value runtime.Object) {
a.lock.Lock() a.lock.Lock()
defer a.lock.Unlock() defer a.lock.Unlock()
a.bootstrapped = true
a.mergedConfiguration = value a.mergedConfiguration = value
a.ready = true a.ready = true
a.lastErr = nil a.lastErr = nil

View File

@@ -0,0 +1,93 @@
/*
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 configuration
import (
"fmt"
"math"
"sync"
"testing"
"time"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
)
func TestTolerateBootstrapFailure(t *testing.T) {
var fakeGetSucceed bool
var fakeGetSucceedLock sync.RWMutex
fakeGetFn := func() (runtime.Object, error) {
fakeGetSucceedLock.RLock()
defer fakeGetSucceedLock.RUnlock()
if fakeGetSucceed {
return nil, nil
} else {
return nil, fmt.Errorf("this error shouldn't be exposed to caller")
}
}
poller := newPoller(fakeGetFn)
poller.bootstrapGracePeriod = 100 * time.Second
poller.bootstrapRetries = math.MaxInt32
// set failureThreshold to 0 so that one single failure will set "ready" to false.
poller.failureThreshold = 0
stopCh := make(chan struct{})
defer close(stopCh)
go poller.Run(stopCh)
go func() {
// The test might have false negative, but won't be flaky
timer := time.NewTimer(2 * time.Second)
<-timer.C
fakeGetSucceedLock.Lock()
defer fakeGetSucceedLock.Unlock()
fakeGetSucceed = true
}()
done := make(chan struct{})
go func(t *testing.T) {
_, err := poller.configuration()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
close(done)
}(t)
<-done
}
func TestNotTolerateNonbootstrapFailure(t *testing.T) {
fakeGetFn := func() (runtime.Object, error) {
return nil, fmt.Errorf("this error should be exposed to caller")
}
poller := newPoller(fakeGetFn)
poller.bootstrapGracePeriod = 1 * time.Second
poller.interval = 1 * time.Millisecond
stopCh := make(chan struct{})
defer close(stopCh)
go poller.Run(stopCh)
// to kick the bootstrap timer
go poller.configuration()
wait.PollInfinite(1*time.Second, func() (bool, error) {
poller.lock.Lock()
defer poller.lock.Unlock()
return poller.bootstrapped, nil
})
_, err := poller.configuration()
if err == nil {
t.Errorf("unexpected no error")
}
}

View File

@@ -22,7 +22,6 @@ import (
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
) )
// TestAuthorizer is a testing struct for testing that fulfills the authorizer interface. // TestAuthorizer is a testing struct for testing that fulfills the authorizer interface.
@@ -123,26 +122,3 @@ func TestWantsClientCert(t *testing.T) {
t.Errorf("plumbing fail - %v %v", ccw.gotCert, ccw.gotKey) t.Errorf("plumbing fail - %v %v", ccw.gotCert, ccw.gotKey)
} }
} }
type fakeHookSource struct{}
func (f *fakeHookSource) List() ([]admissionregistration.ExternalAdmissionHook, error) {
return nil, nil
}
type hookSourceWanter struct {
doNothingAdmission
got WebhookSource
}
func (s *hookSourceWanter) SetWebhookSource(w WebhookSource) { s.got = w }
func TestWantsWebhookSource(t *testing.T) {
hsw := &hookSourceWanter{}
fhs := &fakeHookSource{}
i := &PluginInitializer{}
i.SetWebhookSource(fhs).Initialize(hsw)
if got, ok := hsw.got.(*fakeHookSource); !ok || got != fhs {
t.Errorf("plumbing fail - %v %v#", ok, got)
}
}

View File

@@ -22,7 +22,6 @@ import (
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset" "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
@@ -83,23 +82,12 @@ type WantsClientCert interface {
SetClientCert(cert, key []byte) SetClientCert(cert, key []byte)
} }
// WantsWebhookSource defines a function that accepts a webhook lister for the
// dynamic webhook plugin.
type WantsWebhookSource interface {
SetWebhookSource(WebhookSource)
}
// ServiceResolver knows how to convert a service reference into an actual // ServiceResolver knows how to convert a service reference into an actual
// location. // location.
type ServiceResolver interface { type ServiceResolver interface {
ResolveEndpoint(namespace, name string) (*url.URL, error) ResolveEndpoint(namespace, name string) (*url.URL, error)
} }
// WebhookSource can list dynamic webhook plugins.
type WebhookSource interface {
List() ([]admissionregistration.ExternalAdmissionHook, error)
}
type PluginInitializer struct { type PluginInitializer struct {
internalClient internalclientset.Interface internalClient internalclientset.Interface
externalClient clientset.Interface externalClient clientset.Interface
@@ -109,7 +97,6 @@ type PluginInitializer struct {
restMapper meta.RESTMapper restMapper meta.RESTMapper
quotaRegistry quota.Registry quotaRegistry quota.Registry
serviceResolver ServiceResolver serviceResolver ServiceResolver
webhookSource WebhookSource
// for proving we are apiserver in call-outs // for proving we are apiserver in call-outs
clientCert []byte clientCert []byte
@@ -155,13 +142,6 @@ func (i *PluginInitializer) SetClientCert(cert, key []byte) *PluginInitializer {
return i return i
} }
// SetWebhookSource sets the webhook source-- admittedly this is probably
// specific to the external admission hook plugin.
func (i *PluginInitializer) SetWebhookSource(w WebhookSource) *PluginInitializer {
i.webhookSource = w
return i
}
// Initialize checks the initialization interfaces implemented by each plugin // Initialize checks the initialization interfaces implemented by each plugin
// and provide the appropriate initialization data // and provide the appropriate initialization data
func (i *PluginInitializer) Initialize(plugin admission.Interface) { func (i *PluginInitializer) Initialize(plugin admission.Interface) {
@@ -206,11 +186,4 @@ func (i *PluginInitializer) Initialize(plugin admission.Interface) {
} }
wants.SetClientCert(i.clientCert, i.clientKey) wants.SetClientCert(i.clientCert, i.clientKey)
} }
if wants, ok := plugin.(WantsWebhookSource); ok {
if i.webhookSource == nil {
panic("An admission plugin wants a webhook source, but it was not provided.")
}
wants.SetWebhookSource(i.webhookSource)
}
} }

View File

@@ -21,7 +21,7 @@ go_test(
"//pkg/api:go_default_library", "//pkg/api:go_default_library",
"//pkg/apis/admission/install:go_default_library", "//pkg/apis/admission/install:go_default_library",
"//pkg/apis/admission/v1alpha1:go_default_library", "//pkg/apis/admission/v1alpha1:go_default_library",
"//pkg/apis/admissionregistration:go_default_library", "//pkg/apis/admissionregistration/v1alpha1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema: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/apiserver/pkg/admission:go_default_library",
@@ -41,14 +41,18 @@ go_library(
"//pkg/api:go_default_library", "//pkg/api:go_default_library",
"//pkg/apis/admission/install:go_default_library", "//pkg/apis/admission/install:go_default_library",
"//pkg/apis/admission/v1alpha1:go_default_library", "//pkg/apis/admission/v1alpha1:go_default_library",
"//pkg/apis/admissionregistration:go_default_library", "//pkg/apis/admissionregistration/v1alpha1:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library", "//pkg/kubeapiserver/admission:go_default_library",
"//pkg/kubeapiserver/admission/configuration:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//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:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library",
], ],

View File

@@ -27,16 +27,20 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
admissionv1alpha1 "k8s.io/kubernetes/pkg/apis/admission/v1alpha1" admissionv1alpha1 "k8s.io/kubernetes/pkg/apis/admission/v1alpha1"
"k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
admissioninit "k8s.io/kubernetes/pkg/kubeapiserver/admission" admissioninit "k8s.io/kubernetes/pkg/kubeapiserver/admission"
"k8s.io/kubernetes/pkg/kubeapiserver/admission/configuration"
// install the clientgo admission API for use with api registry // install the clientgo admission API for use with api registry
_ "k8s.io/kubernetes/pkg/apis/admission/install" _ "k8s.io/kubernetes/pkg/apis/admission/install"
@@ -72,6 +76,12 @@ func Register(plugins *admission.Plugins) {
}) })
} }
// WebhookSource can list dynamic webhook plugins.
type WebhookSource interface {
Run(stopCh <-chan struct{})
ExternalAdmissionHooks() (*v1alpha1.ExternalAdmissionHookConfiguration, error)
}
// NewGenericAdmissionWebhook returns a generic admission webhook plugin. // NewGenericAdmissionWebhook returns a generic admission webhook plugin.
func NewGenericAdmissionWebhook() (*GenericAdmissionWebhook, error) { func NewGenericAdmissionWebhook() (*GenericAdmissionWebhook, error) {
return &GenericAdmissionWebhook{ return &GenericAdmissionWebhook{
@@ -90,7 +100,7 @@ func NewGenericAdmissionWebhook() (*GenericAdmissionWebhook, error) {
// GenericAdmissionWebhook is an implementation of admission.Interface. // GenericAdmissionWebhook is an implementation of admission.Interface.
type GenericAdmissionWebhook struct { type GenericAdmissionWebhook struct {
*admission.Handler *admission.Handler
hookSource admissioninit.WebhookSource hookSource WebhookSource
serviceResolver admissioninit.ServiceResolver serviceResolver admissioninit.ServiceResolver
negotiatedSerializer runtime.NegotiatedSerializer negotiatedSerializer runtime.NegotiatedSerializer
clientCert []byte clientCert []byte
@@ -100,7 +110,7 @@ type GenericAdmissionWebhook struct {
var ( var (
_ = admissioninit.WantsServiceResolver(&GenericAdmissionWebhook{}) _ = admissioninit.WantsServiceResolver(&GenericAdmissionWebhook{})
_ = admissioninit.WantsClientCert(&GenericAdmissionWebhook{}) _ = admissioninit.WantsClientCert(&GenericAdmissionWebhook{})
_ = admissioninit.WantsWebhookSource(&GenericAdmissionWebhook{}) _ = admissioninit.WantsExternalKubeClientSet(&GenericAdmissionWebhook{})
) )
func (a *GenericAdmissionWebhook) SetServiceResolver(sr admissioninit.ServiceResolver) { func (a *GenericAdmissionWebhook) SetServiceResolver(sr admissioninit.ServiceResolver) {
@@ -112,23 +122,51 @@ func (a *GenericAdmissionWebhook) SetClientCert(cert, key []byte) {
a.clientKey = key a.clientKey = key
} }
func (a *GenericAdmissionWebhook) SetWebhookSource(ws admissioninit.WebhookSource) { func (a *GenericAdmissionWebhook) SetExternalKubeClientSet(client clientset.Interface) {
a.hookSource = ws a.hookSource = configuration.NewExternalAdmissionHookConfigurationManager(client.Admissionregistration().ExternalAdmissionHookConfigurations())
}
func (a *GenericAdmissionWebhook) Validate() error {
if a.hookSource == nil {
return fmt.Errorf("the GenericAdmissionWebhook admission plugin requires a Kubernetes client to be provided")
}
go a.hookSource.Run(wait.NeverStop)
return nil
}
func (a *GenericAdmissionWebhook) loadConfiguration(attr admission.Attributes) (*v1alpha1.ExternalAdmissionHookConfiguration, error) {
hookConfig, err := a.hookSource.ExternalAdmissionHooks()
// if ExternalAdmissionHook configuration is disabled, fail open
if err == configuration.ErrDisabled {
return &v1alpha1.ExternalAdmissionHookConfiguration{}, nil
}
if err != nil {
e := apierrors.NewServerTimeout(attr.GetResource().GroupResource(), string(attr.GetOperation()), 1)
e.ErrStatus.Message = fmt.Sprintf("Unable to refresh the ExternalAdmissionHook configuration: %v", err)
e.ErrStatus.Reason = "LoadingConfiguration"
e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{
Type: "ExternalAdmissionHookConfigurationFailure",
Message: "An error has occurred while refreshing the externalAdmissionHook configuration, no resources can be created/updated/deleted/connected until a refresh succeeds.",
})
return nil, e
}
return hookConfig, nil
} }
// Admit makes an admission decision based on the request attributes. // Admit makes an admission decision based on the request attributes.
func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error { func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error {
hooks, err := a.hookSource.List() hookConfig, err := a.loadConfiguration(attr)
if err != nil { if err != nil {
return fmt.Errorf("failed listing hooks: %v", err) return err
} }
hooks := hookConfig.ExternalAdmissionHooks
ctx := context.TODO() ctx := context.TODO()
errCh := make(chan error, len(hooks)) errCh := make(chan error, len(hooks))
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(len(hooks)) wg.Add(len(hooks))
for i := range hooks { for i := range hooks {
go func(hook *admissionregistration.ExternalAdmissionHook) { go func(hook *v1alpha1.ExternalAdmissionHook) {
defer wg.Done() defer wg.Done()
if err := a.callHook(ctx, hook, attr); err == nil { if err := a.callHook(ctx, hook, attr); err == nil {
return return
@@ -161,7 +199,7 @@ func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error {
return errs[0] return errs[0]
} }
func (a *GenericAdmissionWebhook) callHook(ctx context.Context, h *admissionregistration.ExternalAdmissionHook, attr admission.Attributes) error { func (a *GenericAdmissionWebhook) callHook(ctx context.Context, h *v1alpha1.ExternalAdmissionHook, attr admission.Attributes) error {
matches := false matches := false
for _, r := range h.Rules { for _, r := range h.Rules {
m := RuleMatcher{Rule: r, Attr: attr} m := RuleMatcher{Rule: r, Attr: attr}
@@ -197,7 +235,7 @@ func (a *GenericAdmissionWebhook) callHook(ctx context.Context, h *admissionregi
} }
} }
func (a *GenericAdmissionWebhook) hookClient(h *admissionregistration.ExternalAdmissionHook) (*rest.RESTClient, error) { func (a *GenericAdmissionWebhook) hookClient(h *v1alpha1.ExternalAdmissionHook) (*rest.RESTClient, error) {
u, err := a.serviceResolver.ResolveEndpoint(h.ClientConfig.Service.Namespace, h.ClientConfig.Service.Name) u, err := a.serviceResolver.ResolveEndpoint(h.ClientConfig.Service.Namespace, h.ClientConfig.Service.Name)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -32,23 +32,25 @@ import (
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/admission/v1alpha1" "k8s.io/kubernetes/pkg/apis/admission/v1alpha1"
"k8s.io/kubernetes/pkg/apis/admissionregistration" registrationv1alpha1 "k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1"
_ "k8s.io/kubernetes/pkg/apis/admission/install" _ "k8s.io/kubernetes/pkg/apis/admission/install"
) )
type fakeHookSource struct { type fakeHookSource struct {
hooks []admissionregistration.ExternalAdmissionHook hooks []registrationv1alpha1.ExternalAdmissionHook
err error err error
} }
func (f *fakeHookSource) List() ([]admissionregistration.ExternalAdmissionHook, error) { func (f *fakeHookSource) ExternalAdmissionHooks() (*registrationv1alpha1.ExternalAdmissionHookConfiguration, error) {
if f.err != nil { if f.err != nil {
return nil, f.err return nil, f.err
} }
return f.hooks, nil return &registrationv1alpha1.ExternalAdmissionHookConfiguration{ExternalAdmissionHooks: f.hooks}, nil
} }
func (f *fakeHookSource) Run(stopCh <-chan struct{}) {}
type fakeServiceResolver struct { type fakeServiceResolver struct {
base url.URL base url.URL
} }
@@ -124,17 +126,17 @@ func TestAdmit(t *testing.T) {
expectAllow bool expectAllow bool
errorContains string errorContains string
} }
ccfg := func(result string) admissionregistration.AdmissionHookClientConfig { ccfg := func(result string) registrationv1alpha1.AdmissionHookClientConfig {
return admissionregistration.AdmissionHookClientConfig{ return registrationv1alpha1.AdmissionHookClientConfig{
Service: admissionregistration.ServiceReference{ Service: registrationv1alpha1.ServiceReference{
Name: result, Name: result,
}, },
CABundle: caCert, CABundle: caCert,
} }
} }
matchEverythingRules := []admissionregistration.RuleWithOperations{{ matchEverythingRules := []registrationv1alpha1.RuleWithOperations{{
Operations: []admissionregistration.OperationType{admissionregistration.OperationAll}, Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.OperationAll},
Rule: admissionregistration.Rule{ Rule: registrationv1alpha1.Rule{
APIGroups: []string{"*"}, APIGroups: []string{"*"},
APIVersions: []string{"*"}, APIVersions: []string{"*"},
Resources: []string{"*/*"}, Resources: []string{"*/*"},
@@ -144,11 +146,11 @@ func TestAdmit(t *testing.T) {
table := map[string]test{ table := map[string]test{
"no match": { "no match": {
hookSource: fakeHookSource{ hookSource: fakeHookSource{
hooks: []admissionregistration.ExternalAdmissionHook{{ hooks: []registrationv1alpha1.ExternalAdmissionHook{{
Name: "nomatch", Name: "nomatch",
ClientConfig: ccfg("disallow"), ClientConfig: ccfg("disallow"),
Rules: []admissionregistration.RuleWithOperations{{ Rules: []registrationv1alpha1.RuleWithOperations{{
Operations: []admissionregistration.OperationType{admissionregistration.Create}, Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.Create},
}}, }},
}}, }},
}, },
@@ -156,7 +158,7 @@ func TestAdmit(t *testing.T) {
}, },
"match & allow": { "match & allow": {
hookSource: fakeHookSource{ hookSource: fakeHookSource{
hooks: []admissionregistration.ExternalAdmissionHook{{ hooks: []registrationv1alpha1.ExternalAdmissionHook{{
Name: "allow", Name: "allow",
ClientConfig: ccfg("allow"), ClientConfig: ccfg("allow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@@ -166,7 +168,7 @@ func TestAdmit(t *testing.T) {
}, },
"match & disallow": { "match & disallow": {
hookSource: fakeHookSource{ hookSource: fakeHookSource{
hooks: []admissionregistration.ExternalAdmissionHook{{ hooks: []registrationv1alpha1.ExternalAdmissionHook{{
Name: "disallow", Name: "disallow",
ClientConfig: ccfg("disallow"), ClientConfig: ccfg("disallow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@@ -176,7 +178,7 @@ func TestAdmit(t *testing.T) {
}, },
"match & disallow ii": { "match & disallow ii": {
hookSource: fakeHookSource{ hookSource: fakeHookSource{
hooks: []admissionregistration.ExternalAdmissionHook{{ hooks: []registrationv1alpha1.ExternalAdmissionHook{{
Name: "disallowReason", Name: "disallowReason",
ClientConfig: ccfg("disallowReason"), ClientConfig: ccfg("disallowReason"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@@ -186,7 +188,7 @@ func TestAdmit(t *testing.T) {
}, },
"match & fail (but allow because fail open)": { "match & fail (but allow because fail open)": {
hookSource: fakeHookSource{ hookSource: fakeHookSource{
hooks: []admissionregistration.ExternalAdmissionHook{{ hooks: []registrationv1alpha1.ExternalAdmissionHook{{
Name: "internalErr A", Name: "internalErr A",
ClientConfig: ccfg("internalErr"), ClientConfig: ccfg("internalErr"),
Rules: matchEverythingRules, Rules: matchEverythingRules,

View File

@@ -21,11 +21,11 @@ import (
"strings" "strings"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1"
) )
type RuleMatcher struct { type RuleMatcher struct {
Rule admissionregistration.RuleWithOperations Rule v1alpha1.RuleWithOperations
Attr admission.Attributes Attr admission.Attributes
} }
@@ -60,12 +60,12 @@ func (r *RuleMatcher) version() bool {
func (r *RuleMatcher) operation() bool { func (r *RuleMatcher) operation() bool {
attrOp := r.Attr.GetOperation() attrOp := r.Attr.GetOperation()
for _, op := range r.Rule.Operations { for _, op := range r.Rule.Operations {
if op == admissionregistration.OperationAll { if op == v1alpha1.OperationAll {
return true return true
} }
// The constants are the same such that this is a valid cast (and this // The constants are the same such that this is a valid cast (and this
// is tested). // is tested).
if op == admissionregistration.OperationType(attrOp) { if op == v1alpha1.OperationType(attrOp) {
return true return true
} }
} }

View File

@@ -21,7 +21,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
adreg "k8s.io/kubernetes/pkg/apis/admissionregistration" adreg "k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1"
) )
type ruleTest struct { type ruleTest struct {