make admission config scheme configurable

pull/6/head
David Eads 2017-11-17 08:18:54 -05:00
parent 4dd136050f
commit ccd4f4a4b6
10 changed files with 73 additions and 34 deletions

View File

@ -28,6 +28,7 @@ import (
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
"k8s.io/kubernetes/pkg/quota"
resourcequotaapi "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota"
resourcequotaapiv1alpha1 "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1"
"k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota/validation"
)
@ -48,6 +49,10 @@ func Register(plugins *admission.Plugins) {
}
return NewResourceQuota(configuration, 5, make(chan struct{}))
})
// add our config types
resourcequotaapi.AddToScheme(plugins.ConfigScheme)
resourcequotaapiv1alpha1.AddToScheme(plugins.ConfigScheme)
}
// QuotaAdmission implements an admission controller that can enforce quota constraints

View File

@ -21,6 +21,7 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/apiserver:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1:go_default_library",
],
)
@ -41,15 +42,12 @@ go_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/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered: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/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/apiserver:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/apiserver/install:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
],

View File

@ -29,27 +29,14 @@ import (
"bytes"
"k8s.io/apimachinery/pkg/apimachinery/announced"
"k8s.io/apimachinery/pkg/apimachinery/registered"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/apis/apiserver"
"k8s.io/apiserver/pkg/apis/apiserver/install"
apiserverv1alpha1 "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1"
)
var (
groupFactoryRegistry = make(announced.APIGroupFactoryRegistry)
registry = registered.NewOrDie(os.Getenv("KUBE_API_VERSIONS"))
scheme = runtime.NewScheme()
codecs = serializer.NewCodecFactory(scheme)
)
func init() {
install.Install(groupFactoryRegistry, registry, scheme)
}
func makeAbs(path, base string) (string, error) {
if filepath.IsAbs(path) {
return path, nil
@ -70,7 +57,7 @@ func makeAbs(path, base string) (string, error) {
// set of pluginNames whose config location references the specified configFilePath.
// It does this to preserve backward compatibility when admission control files were opaque.
// It returns an error if the file did not exist.
func ReadAdmissionConfiguration(pluginNames []string, configFilePath string) (ConfigProvider, error) {
func ReadAdmissionConfiguration(pluginNames []string, configFilePath string, configScheme *runtime.Scheme) (ConfigProvider, error) {
if configFilePath == "" {
return configProvider{config: &apiserver.AdmissionConfiguration{}}, nil
}
@ -79,6 +66,7 @@ func ReadAdmissionConfiguration(pluginNames []string, configFilePath string) (Co
if err != nil {
return nil, fmt.Errorf("unable to read admission control configuration from %q [%v]", configFilePath, err)
}
codecs := serializer.NewCodecFactory(configScheme)
decoder := codecs.UniversalDecoder()
decodedObj, err := runtime.Decode(decoder, data)
// we were able to decode the file successfully
@ -99,7 +87,10 @@ func ReadAdmissionConfiguration(pluginNames []string, configFilePath string) (Co
}
decodedConfig.Plugins[i].Path = absPath
}
return configProvider{config: decodedConfig}, nil
return configProvider{
config: decodedConfig,
scheme: configScheme,
}, nil
}
// we got an error where the decode wasn't related to a missing type
if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) {
@ -119,25 +110,29 @@ func ReadAdmissionConfiguration(pluginNames []string, configFilePath string) (Co
Path: configFilePath})
}
}
scheme.Default(externalConfig)
configScheme.Default(externalConfig)
internalConfig := &apiserver.AdmissionConfiguration{}
if err := scheme.Convert(externalConfig, internalConfig, nil); err != nil {
if err := configScheme.Convert(externalConfig, internalConfig, nil); err != nil {
return nil, err
}
return configProvider{config: internalConfig}, nil
return configProvider{
config: internalConfig,
scheme: configScheme,
}, nil
}
type configProvider struct {
config *apiserver.AdmissionConfiguration
scheme *runtime.Scheme
}
// GetAdmissionPluginConfigurationFor returns a reader that holds the admission plugin configuration.
func GetAdmissionPluginConfigurationFor(pluginCfg apiserver.AdmissionPluginConfiguration) (io.Reader, error) {
func GetAdmissionPluginConfigurationFor(pluginCfg apiserver.AdmissionPluginConfiguration, scheme *runtime.Scheme) (io.Reader, error) {
// if there is nothing nested in the object, we return the named location
obj := pluginCfg.Configuration
if obj != nil {
// serialize the configuration and build a reader for it
content, err := writeYAML(obj)
content, err := writeYAML(obj, scheme)
if err != nil {
return nil, err
}
@ -168,7 +163,7 @@ func (p configProvider) ConfigFor(pluginName string) (io.Reader, error) {
if pluginName != pluginCfg.Name {
continue
}
pluginConfig, err := GetAdmissionPluginConfigurationFor(pluginCfg)
pluginConfig, err := GetAdmissionPluginConfigurationFor(pluginCfg, p.scheme)
if err != nil {
return nil, err
}
@ -179,8 +174,17 @@ func (p configProvider) ConfigFor(pluginName string) (io.Reader, error) {
}
// writeYAML writes the specified object to a byte array as yaml.
func writeYAML(obj runtime.Object) ([]byte, error) {
json, err := runtime.Encode(codecs.LegacyCodec(), obj)
func writeYAML(obj runtime.Object, scheme *runtime.Scheme) ([]byte, error) {
gvks, _, err := scheme.ObjectKinds(obj)
if err != nil {
return nil, err
}
gvs := []schema.GroupVersion{}
for _, gvk := range gvks {
gvs = append(gvs, gvk.GroupVersion())
}
codecs := serializer.NewCodecFactory(scheme)
json, err := runtime.Encode(codecs.LegacyCodec(gvs...), obj)
if err != nil {
return nil, err
}

View File

@ -22,7 +22,10 @@ import (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/apis/apiserver"
apiserverapi "k8s.io/apiserver/pkg/apis/apiserver"
apiserverapiv1alpha1 "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1"
)
func TestReadAdmissionConfiguration(t *testing.T) {
@ -132,11 +135,16 @@ func TestReadAdmissionConfiguration(t *testing.T) {
PluginNames: []string{"NamespaceLifecycle", "InitialResources"},
},
}
scheme := runtime.NewScheme()
apiserverapi.AddToScheme(scheme)
apiserverapiv1alpha1.AddToScheme(scheme)
for testName, testCase := range testCases {
if err = ioutil.WriteFile(configFileName, []byte(testCase.ConfigBody), 0644); err != nil {
t.Fatalf("unexpected err writing temp file: %v", err)
}
config, err := ReadAdmissionConfiguration(testCase.PluginNames, configFileName)
config, err := ReadAdmissionConfiguration(testCase.PluginNames, configFileName, scheme)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}

View File

@ -13,14 +13,12 @@ go_library(
deps = [
"//vendor/github.com/hashicorp/golang-lru:go_default_library",
"//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/install:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",

View File

@ -23,6 +23,8 @@ go_library(
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/metrics:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request:go_default_library",

View File

@ -40,6 +40,8 @@ import (
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
webhookadmissionapi "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission"
webhookadmissionapiv1alpha1 "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1"
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
"k8s.io/apiserver/pkg/admission/plugin/webhook/request"
@ -64,6 +66,9 @@ func Register(plugins *admission.Plugins) {
return plugin, nil
})
// add our config types
webhookadmissionapi.AddToScheme(plugins.ConfigScheme)
webhookadmissionapiv1alpha1.AddToScheme(plugins.ConfigScheme)
}
// WebhookSource can list dynamic webhook plugins.

View File

@ -25,6 +25,8 @@ import (
"sort"
"sync"
"k8s.io/apimachinery/pkg/runtime"
"github.com/golang/glog"
)
@ -37,6 +39,16 @@ type Factory func(config io.Reader) (Interface, error)
type Plugins struct {
lock sync.Mutex
registry map[string]Factory
// ConfigScheme is used to parse the admission plugin config file.
// It is exposed to act as a hook for extending server providing their own config.
ConfigScheme *runtime.Scheme
}
func NewPlugins() *Plugins {
return &Plugins{
ConfigScheme: runtime.NewScheme(),
}
}
// All registered admission options.

View File

@ -35,6 +35,8 @@ go_library(
"//vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/apiserver:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/audit:go_default_library",
"//vendor/k8s.io/apiserver/pkg/audit/policy:go_default_library",

View File

@ -29,6 +29,8 @@ import (
"k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle"
mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
apiserverapi "k8s.io/apiserver/pkg/apis/apiserver"
apiserverapiv1alpha1 "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1"
"k8s.io/apiserver/pkg/server"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
@ -43,7 +45,8 @@ type AdmissionOptions struct {
DefaultOffPlugins []string
PluginNames []string
ConfigFile string
Plugins *admission.Plugins
Plugins *admission.Plugins
}
// NewAdmissionOptions creates a new instance of AdmissionOptions
@ -56,11 +59,13 @@ type AdmissionOptions struct {
// Servers that do care can overwrite/append that field after creation.
func NewAdmissionOptions() *AdmissionOptions {
options := &AdmissionOptions{
Plugins: &admission.Plugins{},
Plugins: admission.NewPlugins(),
PluginNames: []string{},
RecommendedPluginOrder: []string{mutatingwebhook.PluginName, lifecycle.PluginName, initialization.PluginName, validatingwebhook.PluginName},
DefaultOffPlugins: []string{mutatingwebhook.PluginName, initialization.PluginName, validatingwebhook.PluginName},
}
apiserverapi.AddToScheme(options.Plugins.ConfigScheme)
apiserverapiv1alpha1.AddToScheme(options.Plugins.ConfigScheme)
server.RegisterAllAdmissionPlugins(options.Plugins)
return options
}
@ -96,7 +101,7 @@ func (a *AdmissionOptions) ApplyTo(
pluginNames = a.enabledPluginNames()
}
pluginsConfigProvider, err := admission.ReadAdmissionConfiguration(pluginNames, a.ConfigFile)
pluginsConfigProvider, err := admission.ReadAdmissionConfiguration(pluginNames, a.ConfigFile, a.Plugins.ConfigScheme)
if err != nil {
return fmt.Errorf("failed to read plugin config: %v", err)
}