add featuregate inspection as admission plugin initializer

This commit is contained in:
David Eads 2019-11-05 14:28:40 -05:00
parent ed3cc6afea
commit 675c2fb924
19 changed files with 63 additions and 21 deletions

View File

@ -38,6 +38,7 @@ import (
genericapiserver "k8s.io/apiserver/pkg/server" genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/healthz" "k8s.io/apiserver/pkg/server/healthz"
genericoptions "k8s.io/apiserver/pkg/server/options" genericoptions "k8s.io/apiserver/pkg/server/options"
"k8s.io/apiserver/pkg/util/feature"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
kubeexternalinformers "k8s.io/client-go/informers" kubeexternalinformers "k8s.io/client-go/informers"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
@ -73,6 +74,7 @@ func createAggregatorConfig(
&genericConfig, &genericConfig,
externalInformers, externalInformers,
genericConfig.LoopbackClientConfig, genericConfig.LoopbackClientConfig,
feature.DefaultFeatureGate,
pluginInitializers...) pluginInitializers...)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -30,6 +30,7 @@ import (
"k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/features"
genericapiserver "k8s.io/apiserver/pkg/server" genericapiserver "k8s.io/apiserver/pkg/server"
genericoptions "k8s.io/apiserver/pkg/server/options" genericoptions "k8s.io/apiserver/pkg/server/options"
"k8s.io/apiserver/pkg/util/feature"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/apiserver/pkg/util/webhook" "k8s.io/apiserver/pkg/util/webhook"
kubeexternalinformers "k8s.io/client-go/informers" kubeexternalinformers "k8s.io/client-go/informers"
@ -57,6 +58,7 @@ func createAPIExtensionsConfig(
&genericConfig, &genericConfig,
externalInformers, externalInformers,
genericConfig.LoopbackClientConfig, genericConfig.LoopbackClientConfig,
feature.DefaultFeatureGate,
pluginInitializers...) pluginInitializers...)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -48,6 +48,7 @@ import (
serveroptions "k8s.io/apiserver/pkg/server/options" serveroptions "k8s.io/apiserver/pkg/server/options"
serverstorage "k8s.io/apiserver/pkg/server/storage" serverstorage "k8s.io/apiserver/pkg/server/storage"
"k8s.io/apiserver/pkg/storage/etcd3/preflight" "k8s.io/apiserver/pkg/storage/etcd3/preflight"
"k8s.io/apiserver/pkg/util/feature"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/apiserver/pkg/util/term" "k8s.io/apiserver/pkg/util/term"
"k8s.io/apiserver/pkg/util/webhook" "k8s.io/apiserver/pkg/util/webhook"
@ -511,6 +512,7 @@ func buildGenericConfig(
genericConfig, genericConfig,
versionedInformers, versionedInformers,
kubeClientConfig, kubeClientConfig,
feature.DefaultFeatureGate,
pluginInitializers...) pluginInitializers...)
if err != nil { if err != nil {
lastErr = fmt.Errorf("failed to initialize admission: %v", err) lastErr = fmt.Errorf("failed to initialize admission: %v", err)

View File

@ -64,6 +64,7 @@ go_library(
"//staging/src/k8s.io/client-go/informers:go_default_library", "//staging/src/k8s.io/client-go/informers:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library", "//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/component-base/cli/flag:go_default_library", "//staging/src/k8s.io/component-base/cli/flag:go_default_library",
"//staging/src/k8s.io/component-base/featuregate:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/klog:go_default_library", "//vendor/k8s.io/klog:go_default_library",
], ],

View File

@ -28,6 +28,7 @@ import (
genericoptions "k8s.io/apiserver/pkg/server/options" genericoptions "k8s.io/apiserver/pkg/server/options"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/component-base/featuregate"
) )
// AdmissionOptions holds the admission options. // AdmissionOptions holds the admission options.
@ -107,6 +108,7 @@ func (a *AdmissionOptions) ApplyTo(
c *server.Config, c *server.Config,
informers informers.SharedInformerFactory, informers informers.SharedInformerFactory,
kubeAPIServerClientConfig *rest.Config, kubeAPIServerClientConfig *rest.Config,
features featuregate.FeatureGate,
pluginInitializers ...admission.PluginInitializer, pluginInitializers ...admission.PluginInitializer,
) error { ) error {
if a == nil { if a == nil {
@ -118,7 +120,7 @@ func (a *AdmissionOptions) ApplyTo(
a.GenericAdmission.EnablePlugins, a.GenericAdmission.DisablePlugins = computePluginNames(a.PluginNames, a.GenericAdmission.RecommendedPluginOrder) a.GenericAdmission.EnablePlugins, a.GenericAdmission.DisablePlugins = computePluginNames(a.PluginNames, a.GenericAdmission.RecommendedPluginOrder)
} }
return a.GenericAdmission.ApplyTo(c, informers, kubeAPIServerClientConfig, pluginInitializers...) return a.GenericAdmission.ApplyTo(c, informers, kubeAPIServerClientConfig, features, pluginInitializers...)
} }
// explicitly disable all plugins that are not in the enabled list // explicitly disable all plugins that are not in the enabled list

View File

@ -101,7 +101,7 @@ func newGCPermissionsEnforcement() (*gcPermissionsEnforcement, error) {
whiteList: whiteList, whiteList: whiteList,
} }
genericPluginInitializer := initializer.New(nil, nil, fakeAuthorizer{}) genericPluginInitializer := initializer.New(nil, nil, fakeAuthorizer{}, nil)
fakeDiscoveryClient := &fakediscovery.FakeDiscovery{Fake: &coretesting.Fake{}} fakeDiscoveryClient := &fakediscovery.FakeDiscovery{Fake: &coretesting.Fake{}}
fakeDiscoveryClient.Resources = []*metav1.APIResourceList{ fakeDiscoveryClient.Resources = []*metav1.APIResourceList{
{ {

View File

@ -790,7 +790,7 @@ func newHandlerForTest(c clientset.Interface) (*LimitRanger, informers.SharedInf
if err != nil { if err != nil {
return nil, f, err return nil, f, err
} }
pluginInitializer := genericadmissioninitializer.New(c, f, nil) pluginInitializer := genericadmissioninitializer.New(c, f, nil, nil)
pluginInitializer.Initialize(handler) pluginInitializer.Initialize(handler)
err = admission.ValidateInitialization(handler) err = admission.ValidateInitialization(handler)
return handler, f, err return handler, f, err

View File

@ -41,7 +41,7 @@ import (
func newHandlerForTest(c clientset.Interface) (admission.MutationInterface, informers.SharedInformerFactory, error) { func newHandlerForTest(c clientset.Interface) (admission.MutationInterface, informers.SharedInformerFactory, error) {
f := informers.NewSharedInformerFactory(c, 5*time.Minute) f := informers.NewSharedInformerFactory(c, 5*time.Minute)
handler := NewProvision() handler := NewProvision()
pluginInitializer := genericadmissioninitializer.New(c, f, nil) pluginInitializer := genericadmissioninitializer.New(c, f, nil, nil)
pluginInitializer.Initialize(handler) pluginInitializer.Initialize(handler)
err := admission.ValidateInitialization(handler) err := admission.ValidateInitialization(handler)
return handler, f, err return handler, f, err

View File

@ -39,7 +39,7 @@ import (
func newHandlerForTest(c kubernetes.Interface) (admission.ValidationInterface, informers.SharedInformerFactory, error) { func newHandlerForTest(c kubernetes.Interface) (admission.ValidationInterface, informers.SharedInformerFactory, error) {
f := informers.NewSharedInformerFactory(c, 5*time.Minute) f := informers.NewSharedInformerFactory(c, 5*time.Minute)
handler := NewExists() handler := NewExists()
pluginInitializer := genericadmissioninitializer.New(c, f, nil) pluginInitializer := genericadmissioninitializer.New(c, f, nil, nil)
pluginInitializer.Initialize(handler) pluginInitializer.Initialize(handler)
err := admission.ValidateInitialization(handler) err := admission.ValidateInitialization(handler)
return handler, f, err return handler, f, err

View File

@ -198,7 +198,7 @@ func TestHandles(t *testing.T) {
func newHandlerForTest(c kubernetes.Interface) (*Plugin, informers.SharedInformerFactory, error) { func newHandlerForTest(c kubernetes.Interface) (*Plugin, informers.SharedInformerFactory, error) {
f := informers.NewSharedInformerFactory(c, 5*time.Minute) f := informers.NewSharedInformerFactory(c, 5*time.Minute)
handler := NewPodNodeSelector(nil) handler := NewPodNodeSelector(nil)
pluginInitializer := genericadmissioninitializer.New(c, f, nil) pluginInitializer := genericadmissioninitializer.New(c, f, nil, nil)
pluginInitializer.Initialize(handler) pluginInitializer.Initialize(handler)
err := admission.ValidateInitialization(handler) err := admission.ValidateInitialization(handler)
return handler, f, err return handler, f, err

View File

@ -356,7 +356,7 @@ func newHandlerForTest(c kubernetes.Interface) (*Plugin, informers.SharedInforme
return nil, nil, err return nil, nil, err
} }
handler := NewPodTolerationsPlugin(pluginConfig) handler := NewPodTolerationsPlugin(pluginConfig)
pluginInitializer := genericadmissioninitializer.New(c, f, nil) pluginInitializer := genericadmissioninitializer.New(c, f, nil, nil)
pluginInitializer.Initialize(handler) pluginInitializer.Initialize(handler)
err = admission.ValidateInitialization(handler) err = admission.ValidateInitialization(handler)
return handler, f, err return handler, f, err

View File

@ -19,6 +19,7 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//staging/src/k8s.io/client-go/informers:go_default_library", "//staging/src/k8s.io/client-go/informers:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/component-base/featuregate:go_default_library",
], ],
) )

View File

@ -21,25 +21,30 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/component-base/featuregate"
) )
type pluginInitializer struct { type pluginInitializer struct {
externalClient kubernetes.Interface externalClient kubernetes.Interface
externalInformers informers.SharedInformerFactory externalInformers informers.SharedInformerFactory
authorizer authorizer.Authorizer authorizer authorizer.Authorizer
featureGates featuregate.FeatureGate
} }
// New creates an instance of admission plugins initializer. // New creates an instance of admission plugins initializer.
// TODO(p0lyn0mial): make the parameters public, this construction seems to be redundant. // This constructor is public with a long param list so that callers immediately know that new information can be expected
// during compilation when they update a level.
func New( func New(
extClientset kubernetes.Interface, extClientset kubernetes.Interface,
extInformers informers.SharedInformerFactory, extInformers informers.SharedInformerFactory,
authz authorizer.Authorizer, authz authorizer.Authorizer,
featureGates featuregate.FeatureGate,
) pluginInitializer { ) pluginInitializer {
return pluginInitializer{ return pluginInitializer{
externalClient: extClientset, externalClient: extClientset,
externalInformers: extInformers, externalInformers: extInformers,
authorizer: authz, authorizer: authz,
featureGates: featureGates,
} }
} }
@ -57,6 +62,10 @@ func (i pluginInitializer) Initialize(plugin admission.Interface) {
if wants, ok := plugin.(WantsAuthorizer); ok { if wants, ok := plugin.(WantsAuthorizer); ok {
wants.SetAuthorizer(i.authorizer) wants.SetAuthorizer(i.authorizer)
} }
if wants, ok := plugin.(WantsFeatures); ok {
wants.InspectFeatureGates(i.featureGates)
}
} }
var _ admission.PluginInitializer = pluginInitializer{} var _ admission.PluginInitializer = pluginInitializer{}

View File

@ -32,7 +32,7 @@ import (
// TestWantsAuthorizer ensures that the authorizer is injected // TestWantsAuthorizer ensures that the authorizer is injected
// when the WantsAuthorizer interface is implemented by a plugin. // when the WantsAuthorizer interface is implemented by a plugin.
func TestWantsAuthorizer(t *testing.T) { func TestWantsAuthorizer(t *testing.T) {
target := initializer.New(nil, nil, &TestAuthorizer{}) target := initializer.New(nil, nil, &TestAuthorizer{}, nil)
wantAuthorizerAdmission := &WantAuthorizerAdmission{} wantAuthorizerAdmission := &WantAuthorizerAdmission{}
target.Initialize(wantAuthorizerAdmission) target.Initialize(wantAuthorizerAdmission)
if wantAuthorizerAdmission.auth == nil { if wantAuthorizerAdmission.auth == nil {
@ -44,7 +44,7 @@ func TestWantsAuthorizer(t *testing.T) {
// when the WantsExternalKubeClientSet interface is implemented by a plugin. // when the WantsExternalKubeClientSet interface is implemented by a plugin.
func TestWantsExternalKubeClientSet(t *testing.T) { func TestWantsExternalKubeClientSet(t *testing.T) {
cs := &fake.Clientset{} cs := &fake.Clientset{}
target := initializer.New(cs, nil, &TestAuthorizer{}) target := initializer.New(cs, nil, &TestAuthorizer{}, nil)
wantExternalKubeClientSet := &WantExternalKubeClientSet{} wantExternalKubeClientSet := &WantExternalKubeClientSet{}
target.Initialize(wantExternalKubeClientSet) target.Initialize(wantExternalKubeClientSet)
if wantExternalKubeClientSet.cs != cs { if wantExternalKubeClientSet.cs != cs {
@ -57,7 +57,7 @@ func TestWantsExternalKubeClientSet(t *testing.T) {
func TestWantsExternalKubeInformerFactory(t *testing.T) { func TestWantsExternalKubeInformerFactory(t *testing.T) {
cs := &fake.Clientset{} cs := &fake.Clientset{}
sf := informers.NewSharedInformerFactory(cs, time.Duration(1)*time.Second) sf := informers.NewSharedInformerFactory(cs, time.Duration(1)*time.Second)
target := initializer.New(cs, sf, &TestAuthorizer{}) target := initializer.New(cs, sf, &TestAuthorizer{}, nil)
wantExternalKubeInformerFactory := &WantExternalKubeInformerFactory{} wantExternalKubeInformerFactory := &WantExternalKubeInformerFactory{}
target.Initialize(wantExternalKubeInformerFactory) target.Initialize(wantExternalKubeInformerFactory)
if wantExternalKubeInformerFactory.sf != sf { if wantExternalKubeInformerFactory.sf != sf {

View File

@ -21,6 +21,7 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/component-base/featuregate"
) )
// WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it // WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it
@ -40,3 +41,14 @@ type WantsAuthorizer interface {
SetAuthorizer(authorizer.Authorizer) SetAuthorizer(authorizer.Authorizer)
admission.InitializationValidator admission.InitializationValidator
} }
// WantsFeatureGate defines a function which passes the featureGates for inspection by an admission plugin.
// Admission plugins should not hold a reference to the featureGates. Instead, they should query a particular one
// and assign it to a simple bool in the admission plugin struct.
// func (a *admissionPlugin) InspectFeatureGates(features featuregate.FeatureGate){
// a.myFeatureIsOn = features.Enabled("my-feature")
// }
type WantsFeatures interface {
InspectFeatureGates(featuregate.FeatureGate)
admission.InitializationValidator
}

View File

@ -52,7 +52,7 @@ func newHandlerForTestWithClock(c clientset.Interface, cacheClock clock.Clock) (
if err != nil { if err != nil {
return nil, f, err return nil, f, err
} }
pluginInitializer := kubeadmission.New(c, f, nil) pluginInitializer := kubeadmission.New(c, f, nil, nil)
pluginInitializer.Initialize(handler) pluginInitializer.Initialize(handler)
err = admission.ValidateInitialization(handler) err = admission.ValidateInitialization(handler)
return handler, f, err return handler, f, err

View File

@ -82,6 +82,7 @@ go_library(
"//staging/src/k8s.io/client-go/util/cert:go_default_library", "//staging/src/k8s.io/client-go/util/cert:go_default_library",
"//staging/src/k8s.io/client-go/util/keyutil:go_default_library", "//staging/src/k8s.io/client-go/util/keyutil:go_default_library",
"//staging/src/k8s.io/component-base/cli/flag:go_default_library", "//staging/src/k8s.io/component-base/cli/flag:go_default_library",
"//staging/src/k8s.io/component-base/featuregate:go_default_library",
"//vendor/github.com/google/uuid:go_default_library", "//vendor/github.com/google/uuid:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/gopkg.in/natefinch/lumberjack.v2:go_default_library", "//vendor/gopkg.in/natefinch/lumberjack.v2:go_default_library",

View File

@ -37,6 +37,7 @@ import (
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/component-base/featuregate"
) )
var configScheme = runtime.NewScheme() var configScheme = runtime.NewScheme()
@ -117,6 +118,7 @@ func (a *AdmissionOptions) ApplyTo(
c *server.Config, c *server.Config,
informers informers.SharedInformerFactory, informers informers.SharedInformerFactory,
kubeAPIServerClientConfig *rest.Config, kubeAPIServerClientConfig *rest.Config,
features featuregate.FeatureGate,
pluginInitializers ...admission.PluginInitializer, pluginInitializers ...admission.PluginInitializer,
) error { ) error {
if a == nil { if a == nil {
@ -139,7 +141,7 @@ func (a *AdmissionOptions) ApplyTo(
if err != nil { if err != nil {
return err return err
} }
genericInitializer := initializer.New(clientset, informers, c.Authorization.Authorizer) genericInitializer := initializer.New(clientset, informers, c.Authorization.Authorizer, features)
initializersChain := admission.PluginInitializers{} initializersChain := admission.PluginInitializers{}
pluginInitializers = append(pluginInitializers, genericInitializer) pluginInitializers = append(pluginInitializers, genericInitializer)
initializersChain = append(initializersChain, pluginInitializers...) initializersChain = append(initializersChain, pluginInitializers...)

View File

@ -18,11 +18,13 @@ package options
import ( import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/apiserver/pkg/util/feature"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/storage/storagebackend" "k8s.io/apiserver/pkg/storage/storagebackend"
"k8s.io/component-base/featuregate"
) )
// RecommendedOptions contains the recommended options for running an API server. // RecommendedOptions contains the recommended options for running an API server.
@ -37,6 +39,8 @@ type RecommendedOptions struct {
Features *FeatureOptions Features *FeatureOptions
CoreAPI *CoreAPIOptions CoreAPI *CoreAPIOptions
// FeatureGate is a way to plumb feature gate through if you have them.
FeatureGate featuregate.FeatureGate
// ExtraAdmissionInitializers is called once after all ApplyTo from the options above, to pass the returned // ExtraAdmissionInitializers is called once after all ApplyTo from the options above, to pass the returned
// admission plugin initializers to Admission.ApplyTo. // admission plugin initializers to Admission.ApplyTo.
ExtraAdmissionInitializers func(c *server.RecommendedConfig) ([]admission.PluginInitializer, error) ExtraAdmissionInitializers func(c *server.RecommendedConfig) ([]admission.PluginInitializer, error)
@ -58,13 +62,17 @@ func NewRecommendedOptions(prefix string, codec runtime.Codec, processInfo *Proc
sso.HTTP2MaxStreamsPerConnection = 1000 sso.HTTP2MaxStreamsPerConnection = 1000
return &RecommendedOptions{ return &RecommendedOptions{
Etcd: NewEtcdOptions(storagebackend.NewDefaultConfig(prefix, codec)), Etcd: NewEtcdOptions(storagebackend.NewDefaultConfig(prefix, codec)),
SecureServing: sso.WithLoopback(), SecureServing: sso.WithLoopback(),
Authentication: NewDelegatingAuthenticationOptions(), Authentication: NewDelegatingAuthenticationOptions(),
Authorization: NewDelegatingAuthorizationOptions(), Authorization: NewDelegatingAuthorizationOptions(),
Audit: NewAuditOptions(), Audit: NewAuditOptions(),
Features: NewFeatureOptions(), Features: NewFeatureOptions(),
CoreAPI: NewCoreAPIOptions(), CoreAPI: NewCoreAPIOptions(),
// Wired a global by default that sadly people will abuse to have different meanings in different repos.
// Please consider creating your own FeatureGate so you can have a consistent meaning for what a variable contains
// across different repos. Future you will thank you.
FeatureGate: feature.DefaultFeatureGate,
ExtraAdmissionInitializers: func(c *server.RecommendedConfig) ([]admission.PluginInitializer, error) { return nil, nil }, ExtraAdmissionInitializers: func(c *server.RecommendedConfig) ([]admission.PluginInitializer, error) { return nil, nil },
Admission: NewAdmissionOptions(), Admission: NewAdmissionOptions(),
ProcessInfo: processInfo, ProcessInfo: processInfo,
@ -111,7 +119,7 @@ func (o *RecommendedOptions) ApplyTo(config *server.RecommendedConfig) error {
} }
if initializers, err := o.ExtraAdmissionInitializers(config); err != nil { if initializers, err := o.ExtraAdmissionInitializers(config); err != nil {
return err return err
} else if err := o.Admission.ApplyTo(&config.Config, config.SharedInformerFactory, config.ClientConfig, initializers...); err != nil { } else if err := o.Admission.ApplyTo(&config.Config, config.SharedInformerFactory, config.ClientConfig, o.FeatureGate, initializers...); err != nil {
return err return err
} }
if err := o.EgressSelector.ApplyTo(&config.Config); err != nil { if err := o.EgressSelector.ApplyTo(&config.Config); err != nil {