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.
pull/6/head
Kubernetes Submit Queue 2017-06-14 17:13:56 -07:00 committed by GitHub
commit 2939837923
11 changed files with 237 additions and 103 deletions

View File

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

View File

@ -10,12 +10,17 @@ load(
go_test(
name = "go_default_test",
srcs = ["initializer_manager_test.go"],
srcs = [
"configuration_manager_test.go",
"initializer_manager_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/apis/admissionregistration/v1alpha1: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 (
defaultInterval = 1 * time.Second
defaultFailureThreshold = 5
defaultInterval = 1 * time.Second
defaultFailureThreshold = 5
defaultBootstrapRetries = 5
defaultBootstrapGraceperiod = 5 * time.Second
)
var (
@ -47,26 +49,43 @@ type poller struct {
// a function to consistently read the latest configuration
get getFunc
// consistent read interval
// read-only
interval time.Duration
// if the number of consecutive read failure equals or exceeds the failureThreshold , the
// configuration is regarded as not ready.
// read-only
failureThreshold int
// number of consecutive failures so far.
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.
ready bool
mergedConfiguration runtime.Object
// lock much be hold when reading ready or mergedConfiguration
lock sync.RWMutex
lastErr error
lastErr error
// lock must be hold when reading/writing the data fields of poller.
lock sync.RWMutex
}
func newPoller(get getFunc) *poller {
return &poller{
get: get,
interval: defaultInterval,
failureThreshold: defaultFailureThreshold,
p := poller{
get: get,
interval: defaultInterval,
failureThreshold: defaultFailureThreshold,
bootstrapRetries: defaultBootstrapRetries,
bootstrapGracePeriod: defaultBootstrapGraceperiod,
}
return &p
}
func (a *poller) lastError(err error) {
@ -81,21 +100,47 @@ func (a *poller) notReady() {
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) {
a.once.Do(a.bootstrapping)
a.lock.RLock()
defer a.lock.RUnlock()
if !a.ready {
if a.lastErr != nil {
return nil, a.lastErr
}
return nil, ErrNotReady
retries := 1
if !a.bootstrapped {
retries = a.bootstrapRetries
}
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) {
a.lock.Lock()
defer a.lock.Unlock()
a.bootstrapped = true
a.mergedConfiguration = value
a.ready = true
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/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
)
// 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)
}
}
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/apiserver/pkg/admission"
"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/internalclientset"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
@ -83,23 +82,12 @@ type WantsClientCert interface {
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
// location.
type ServiceResolver interface {
ResolveEndpoint(namespace, name string) (*url.URL, error)
}
// WebhookSource can list dynamic webhook plugins.
type WebhookSource interface {
List() ([]admissionregistration.ExternalAdmissionHook, error)
}
type PluginInitializer struct {
internalClient internalclientset.Interface
externalClient clientset.Interface
@ -109,7 +97,6 @@ type PluginInitializer struct {
restMapper meta.RESTMapper
quotaRegistry quota.Registry
serviceResolver ServiceResolver
webhookSource WebhookSource
// for proving we are apiserver in call-outs
clientCert []byte
@ -155,13 +142,6 @@ func (i *PluginInitializer) SetClientCert(cert, key []byte) *PluginInitializer {
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
// and provide the appropriate initialization data
func (i *PluginInitializer) Initialize(plugin admission.Interface) {
@ -206,11 +186,4 @@ func (i *PluginInitializer) Initialize(plugin admission.Interface) {
}
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/apis/admission/install: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/runtime/schema:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
@ -41,14 +41,18 @@ go_library(
"//pkg/api:go_default_library",
"//pkg/apis/admission/install: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/configuration: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/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/apimachinery/pkg/runtime/serializer: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/client-go/rest:go_default_library",
],

View File

@ -27,16 +27,20 @@ import (
"github.com/golang/glog"
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/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/admission"
"k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api"
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"
"k8s.io/kubernetes/pkg/kubeapiserver/admission/configuration"
// install the clientgo admission API for use with api registry
_ "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.
func NewGenericAdmissionWebhook() (*GenericAdmissionWebhook, error) {
return &GenericAdmissionWebhook{
@ -90,7 +100,7 @@ func NewGenericAdmissionWebhook() (*GenericAdmissionWebhook, error) {
// GenericAdmissionWebhook is an implementation of admission.Interface.
type GenericAdmissionWebhook struct {
*admission.Handler
hookSource admissioninit.WebhookSource
hookSource WebhookSource
serviceResolver admissioninit.ServiceResolver
negotiatedSerializer runtime.NegotiatedSerializer
clientCert []byte
@ -100,7 +110,7 @@ type GenericAdmissionWebhook struct {
var (
_ = admissioninit.WantsServiceResolver(&GenericAdmissionWebhook{})
_ = admissioninit.WantsClientCert(&GenericAdmissionWebhook{})
_ = admissioninit.WantsWebhookSource(&GenericAdmissionWebhook{})
_ = admissioninit.WantsExternalKubeClientSet(&GenericAdmissionWebhook{})
)
func (a *GenericAdmissionWebhook) SetServiceResolver(sr admissioninit.ServiceResolver) {
@ -112,23 +122,51 @@ func (a *GenericAdmissionWebhook) SetClientCert(cert, key []byte) {
a.clientKey = key
}
func (a *GenericAdmissionWebhook) SetWebhookSource(ws admissioninit.WebhookSource) {
a.hookSource = ws
func (a *GenericAdmissionWebhook) SetExternalKubeClientSet(client clientset.Interface) {
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.
func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error {
hooks, err := a.hookSource.List()
hookConfig, err := a.loadConfiguration(attr)
if err != nil {
return fmt.Errorf("failed listing hooks: %v", err)
return err
}
hooks := hookConfig.ExternalAdmissionHooks
ctx := context.TODO()
errCh := make(chan error, len(hooks))
wg := sync.WaitGroup{}
wg.Add(len(hooks))
for i := range hooks {
go func(hook *admissionregistration.ExternalAdmissionHook) {
go func(hook *v1alpha1.ExternalAdmissionHook) {
defer wg.Done()
if err := a.callHook(ctx, hook, attr); err == nil {
return
@ -161,7 +199,7 @@ func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error {
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
for _, r := range h.Rules {
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)
if err != nil {
return nil, err

View File

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

View File

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

View File

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