diff --git a/cmd/kube-apiserver/app/aggregator.go b/cmd/kube-apiserver/app/aggregator.go index 623fcad8158..bc2b685b5cc 100644 --- a/cmd/kube-apiserver/app/aggregator.go +++ b/cmd/kube-apiserver/app/aggregator.go @@ -61,6 +61,14 @@ func createAggregatorConfig(kubeAPIServerConfig genericapiserver.Config, command etcdOptions.StorageConfig.Codec = aggregatorapiserver.Codecs.LegacyCodec(v1beta1.SchemeGroupVersion) genericConfig.RESTOptionsGetter = &genericoptions.SimpleRestOptionsFactory{Options: etcdOptions} + // override MergedResourceConfig with aggregator defaults and registry + if err := commandOptions.APIEnablement.ApplyTo( + &genericConfig, + aggregatorapiserver.DefaultAPIResourceConfigSource(), + aggregatorapiserver.Registry); err != nil { + return nil, err + } + var err error var certBytes, keyBytes []byte if len(commandOptions.ProxyClientCertFile) > 0 && len(commandOptions.ProxyClientKeyFile) > 0 { diff --git a/cmd/kube-apiserver/app/apiextensions.go b/cmd/kube-apiserver/app/apiextensions.go index 1ef2916e702..c8dd16e7af9 100644 --- a/cmd/kube-apiserver/app/apiextensions.go +++ b/cmd/kube-apiserver/app/apiextensions.go @@ -39,6 +39,14 @@ func createAPIExtensionsConfig(kubeAPIServerConfig genericapiserver.Config, exte etcdOptions.StorageConfig.Codec = apiextensionsapiserver.Codecs.LegacyCodec(v1beta1.SchemeGroupVersion) genericConfig.RESTOptionsGetter = &genericoptions.SimpleRestOptionsFactory{Options: etcdOptions} + // override MergedResourceConfig with apiextensions defaults and registry + if err := commandOptions.APIEnablement.ApplyTo( + &genericConfig, + apiextensionsapiserver.DefaultAPIResourceConfigSource(), + apiextensionsapiserver.Registry); err != nil { + return nil, err + } + apiextensionsConfig := &apiextensionsapiserver.Config{ GenericConfig: &genericapiserver.RecommendedConfig{ Config: genericConfig, diff --git a/cmd/kube-apiserver/app/options/options.go b/cmd/kube-apiserver/app/options/options.go index 08d51680de5..544cb8f0aad 100644 --- a/cmd/kube-apiserver/app/options/options.go +++ b/cmd/kube-apiserver/app/options/options.go @@ -51,7 +51,7 @@ type ServerRunOptions struct { Authorization *kubeoptions.BuiltInAuthorizationOptions CloudProvider *kubeoptions.CloudProviderOptions StorageSerialization *kubeoptions.StorageSerializationOptions - APIEnablement *kubeoptions.APIEnablementOptions + APIEnablement *genericoptions.APIEnablementOptions AllowPrivileged bool EnableLogsHandler bool @@ -87,7 +87,7 @@ func NewServerRunOptions() *ServerRunOptions { Authorization: kubeoptions.NewBuiltInAuthorizationOptions(), CloudProvider: kubeoptions.NewCloudProviderOptions(), StorageSerialization: kubeoptions.NewStorageSerializationOptions(), - APIEnablement: kubeoptions.NewAPIEnablementOptions(), + APIEnablement: genericoptions.NewAPIEnablementOptions(), EnableLogsHandler: true, EventTTL: 1 * time.Hour, diff --git a/cmd/kube-apiserver/app/options/options_test.go b/cmd/kube-apiserver/app/options/options_test.go index a9fd3760a7a..5b7c9f2aac3 100644 --- a/cmd/kube-apiserver/app/options/options_test.go +++ b/cmd/kube-apiserver/app/options/options_test.go @@ -27,7 +27,7 @@ import ( "k8s.io/apimachinery/pkg/util/diff" apiserveroptions "k8s.io/apiserver/pkg/server/options" "k8s.io/apiserver/pkg/storage/storagebackend" - utilconfig "k8s.io/apiserver/pkg/util/flag" + utilflag "k8s.io/apiserver/pkg/util/flag" auditwebhook "k8s.io/apiserver/plugin/pkg/audit/webhook" restclient "k8s.io/client-go/rest" "k8s.io/kubernetes/pkg/api/legacyscheme" @@ -235,8 +235,8 @@ func TestAddFlags(t *testing.T) { StorageVersions: legacyscheme.Registry.AllPreferredGroupVersions(), DefaultStorageVersions: legacyscheme.Registry.AllPreferredGroupVersions(), }, - APIEnablement: &kubeoptions.APIEnablementOptions{ - RuntimeConfig: utilconfig.ConfigurationMap{}, + APIEnablement: &apiserveroptions.APIEnablementOptions{ + RuntimeConfig: utilflag.ConfigurationMap{}, }, EnableLogsHandler: false, EnableAggregatorRouting: true, diff --git a/cmd/kube-apiserver/app/options/validation.go b/cmd/kube-apiserver/app/options/validation.go index 481552d3b29..ece12a73e48 100644 --- a/cmd/kube-apiserver/app/options/validation.go +++ b/cmd/kube-apiserver/app/options/validation.go @@ -18,6 +18,10 @@ package options import ( "fmt" + + apiextensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver" + aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) // TODO: Longer term we should read this from some config store, rather than a flag. @@ -75,5 +79,9 @@ func (options *ServerRunOptions) Validate() []error { if options.MasterCount <= 0 { errors = append(errors, fmt.Errorf("--apiserver-count should be a positive number, but value '%d' provided", options.MasterCount)) } + if errs := options.APIEnablement.Validate(legacyscheme.Registry, apiextensionsapiserver.Registry, aggregatorapiserver.Registry); len(errs) > 0 { + errors = append(errors, errs...) + } + return errors } diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index 2897197f0a8..87eba74b8be 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -142,6 +142,7 @@ func CreateServerChain(runOptions *options.ServerRunOptions, stopCh <-chan struc if err != nil { return nil, err } + kubeAPIServerConfig, sharedInformers, versionedInformers, insecureServingOptions, serviceResolver, err := CreateKubeAPIServerConfig(runOptions, nodeTunneler, proxyTransport) if err != nil { return nil, err @@ -304,7 +305,7 @@ func CreateKubeAPIServerConfig(s *options.ServerRunOptions, nodeTunneler tunnele return nil, nil, nil, nil, nil, err } - storageFactory, err := BuildStorageFactory(s) + storageFactory, err := BuildStorageFactory(s, genericConfig.MergedResourceConfig) if err != nil { return nil, nil, nil, nil, nil, err } @@ -384,6 +385,9 @@ func BuildGenericConfig(s *options.ServerRunOptions, proxyTransport *http.Transp if err := s.Features.ApplyTo(genericConfig); err != nil { return nil, nil, nil, nil, nil, err } + if err := s.APIEnablement.ApplyTo(genericConfig, master.DefaultAPIResourceConfigSource(), legacyscheme.Registry); err != nil { + return nil, nil, nil, nil, nil, err + } genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(generatedopenapi.GetOpenAPIDefinitions, legacyscheme.Scheme) genericConfig.OpenAPIConfig.PostProcessSpec = postProcessOpenAPISpecForBackwardCompatibility @@ -398,7 +402,7 @@ func BuildGenericConfig(s *options.ServerRunOptions, proxyTransport *http.Transp kubeVersion := version.Get() genericConfig.Version = &kubeVersion - storageFactory, err := BuildStorageFactory(s) + storageFactory, err := BuildStorageFactory(s, genericConfig.MergedResourceConfig) if err != nil { return nil, nil, nil, nil, nil, err } @@ -561,7 +565,7 @@ func BuildAuthorizer(s *options.ServerRunOptions, sharedInformers informers.Shar // BuildStorageFactory constructs the storage factory. If encryption at rest is used, it expects // all supported KMS plugins to be registered in the KMS plugin registry before being called. -func BuildStorageFactory(s *options.ServerRunOptions) (*serverstorage.DefaultStorageFactory, error) { +func BuildStorageFactory(s *options.ServerRunOptions, apiResourceConfig *serverstorage.ResourceConfig) (*serverstorage.DefaultStorageFactory, error) { storageGroupsToEncodingVersion, err := s.StorageSerialization.StorageGroupsToEncodingVersion() if err != nil { return nil, fmt.Errorf("error generating storage version map: %s", err) @@ -577,7 +581,7 @@ func BuildStorageFactory(s *options.ServerRunOptions) (*serverstorage.DefaultSto storage.Resource("volumeattachments").WithVersion("v1alpha1"), admissionregistration.Resource("initializerconfigurations").WithVersion("v1alpha1"), }, - master.DefaultAPIResourceConfigSource(), s.APIEnablement.RuntimeConfig) + apiResourceConfig) if err != nil { return nil, fmt.Errorf("error in initializing storage factory: %s", err) } @@ -698,6 +702,20 @@ func defaultOptions(s *options.ServerRunOptions) error { } } + // TODO: remove when we stop supporting the legacy group version. + if s.APIEnablement.RuntimeConfig != nil { + for key, value := range s.APIEnablement.RuntimeConfig { + if key == "v1" || strings.HasPrefix(key, "v1/") || + key == "api/v1" || strings.HasPrefix(key, "api/v1/") { + delete(s.APIEnablement.RuntimeConfig, key) + s.APIEnablement.RuntimeConfig["/v1"] = value + } + if key == "api/legacy" { + delete(s.APIEnablement.RuntimeConfig, key) + } + } + } + return nil } diff --git a/pkg/kubeapiserver/default_storage_factory_builder.go b/pkg/kubeapiserver/default_storage_factory_builder.go index 4803cce2eb8..fa114e9412a 100644 --- a/pkg/kubeapiserver/default_storage_factory_builder.go +++ b/pkg/kubeapiserver/default_storage_factory_builder.go @@ -17,16 +17,11 @@ limitations under the License. package kubeapiserver import ( - "fmt" - "strconv" - "strings" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/server/resourceconfig" serverstorage "k8s.io/apiserver/pkg/server/storage" "k8s.io/apiserver/pkg/storage/storagebackend" - utilflag "k8s.io/apiserver/pkg/util/flag" - "k8s.io/kubernetes/pkg/api/legacyscheme" ) // SpecialDefaultResourcePrefixes are prefixes compiled into Kubernetes. @@ -41,151 +36,17 @@ var SpecialDefaultResourcePrefixes = map[schema.GroupResource]string{ } // NewStorageFactory builds the DefaultStorageFactory. -// Merges defaultResourceConfig with the user specified overrides and merges -// defaultAPIResourceConfig with the corresponding user specified overrides as well. -func NewStorageFactory(storageConfig storagebackend.Config, defaultMediaType string, serializer runtime.StorageSerializer, - defaultResourceEncoding *serverstorage.DefaultResourceEncodingConfig, storageEncodingOverrides map[string]schema.GroupVersion, resourceEncodingOverrides []schema.GroupVersionResource, - defaultAPIResourceConfig *serverstorage.ResourceConfig, resourceConfigOverrides utilflag.ConfigurationMap) (*serverstorage.DefaultStorageFactory, error) { - - resourceEncodingConfig := mergeGroupEncodingConfigs(defaultResourceEncoding, storageEncodingOverrides) - resourceEncodingConfig = mergeResourceEncodingConfigs(resourceEncodingConfig, resourceEncodingOverrides) - apiResourceConfig, err := mergeAPIResourceConfigs(defaultAPIResourceConfig, resourceConfigOverrides) - if err != nil { - return nil, err - } +// Merges defaultResourceEncoding with the user specified overrides. +func NewStorageFactory( + storageConfig storagebackend.Config, + defaultMediaType string, + serializer runtime.StorageSerializer, + defaultResourceEncoding *serverstorage.DefaultResourceEncodingConfig, + storageEncodingOverrides map[string]schema.GroupVersion, + resourceEncodingOverrides []schema.GroupVersionResource, + apiResourceConfig *serverstorage.ResourceConfig, +) (*serverstorage.DefaultStorageFactory, error) { + resourceEncodingConfig := resourceconfig.MergeGroupEncodingConfigs(defaultResourceEncoding, storageEncodingOverrides) + resourceEncodingConfig = resourceconfig.MergeResourceEncodingConfigs(resourceEncodingConfig, resourceEncodingOverrides) return serverstorage.NewDefaultStorageFactory(storageConfig, defaultMediaType, serializer, resourceEncodingConfig, apiResourceConfig, SpecialDefaultResourcePrefixes), nil } - -// Merges the given defaultResourceConfig with specific GroupVersionResource overrides. -func mergeResourceEncodingConfigs(defaultResourceEncoding *serverstorage.DefaultResourceEncodingConfig, resourceEncodingOverrides []schema.GroupVersionResource) *serverstorage.DefaultResourceEncodingConfig { - resourceEncodingConfig := defaultResourceEncoding - for _, gvr := range resourceEncodingOverrides { - resourceEncodingConfig.SetResourceEncoding(gvr.GroupResource(), gvr.GroupVersion(), - schema.GroupVersion{Group: gvr.Group, Version: runtime.APIVersionInternal}) - } - return resourceEncodingConfig -} - -// Merges the given defaultResourceConfig with specific GroupVersion overrides. -func mergeGroupEncodingConfigs(defaultResourceEncoding *serverstorage.DefaultResourceEncodingConfig, storageEncodingOverrides map[string]schema.GroupVersion) *serverstorage.DefaultResourceEncodingConfig { - resourceEncodingConfig := defaultResourceEncoding - for group, storageEncodingVersion := range storageEncodingOverrides { - resourceEncodingConfig.SetVersionEncoding(group, storageEncodingVersion, schema.GroupVersion{Group: group, Version: runtime.APIVersionInternal}) - } - return resourceEncodingConfig -} - -// Merges the given defaultAPIResourceConfig with the given resourceConfigOverrides. -func mergeAPIResourceConfigs(defaultAPIResourceConfig *serverstorage.ResourceConfig, resourceConfigOverrides utilflag.ConfigurationMap) (*serverstorage.ResourceConfig, error) { - resourceConfig := defaultAPIResourceConfig - overrides := resourceConfigOverrides - - // "api/all=false" allows users to selectively enable specific api versions. - allAPIFlagValue, ok := overrides["api/all"] - if ok { - if allAPIFlagValue == "false" { - // Disable all group versions. - resourceConfig.DisableVersions(legacyscheme.Registry.RegisteredGroupVersions()...) - } else if allAPIFlagValue == "true" { - resourceConfig.EnableVersions(legacyscheme.Registry.RegisteredGroupVersions()...) - } - } - - // "api/legacy=false" allows users to disable legacy api versions. - disableLegacyAPIs := false - legacyAPIFlagValue, ok := overrides["api/legacy"] - if ok && legacyAPIFlagValue == "false" { - disableLegacyAPIs = true - } - _ = disableLegacyAPIs // hush the compiler while we don't have legacy APIs to disable. - - // "={true|false} allows users to enable/disable API. - // This takes preference over api/all and api/legacy, if specified. - // Iterate through all group/version overrides specified in runtimeConfig. - for key := range overrides { - if key == "api/all" || key == "api/legacy" { - // Have already handled them above. Can skip them here. - continue - } - tokens := strings.Split(key, "/") - if len(tokens) != 2 { - continue - } - groupVersionString := tokens[0] + "/" + tokens[1] - // HACK: Hack for "v1" legacy group version. - // Remove when we stop supporting the legacy group version. - if groupVersionString == "api/v1" { - groupVersionString = "v1" - } - groupVersion, err := schema.ParseGroupVersion(groupVersionString) - if err != nil { - return nil, fmt.Errorf("invalid key %s", key) - } - // Verify that the groupVersion is legacyscheme.Registry. - if !legacyscheme.Registry.IsRegisteredVersion(groupVersion) { - return nil, fmt.Errorf("group version %s that has not been registered", groupVersion.String()) - } - enabled, err := getRuntimeConfigValue(overrides, key, false) - if err != nil { - return nil, err - } - if enabled { - resourceConfig.EnableVersions(groupVersion) - } else { - resourceConfig.DisableVersions(groupVersion) - } - } - - // Iterate through all group/version/resource overrides specified in runtimeConfig. - for key := range overrides { - tokens := strings.Split(key, "/") - if len(tokens) != 3 { - continue - } - groupVersionString := tokens[0] + "/" + tokens[1] - // HACK: Hack for "v1" legacy group version. - // Remove when we stop supporting the legacy group version. - if groupVersionString == "api/v1" { - groupVersionString = "v1" - } - groupVersion, err := schema.ParseGroupVersion(groupVersionString) - if err != nil { - return nil, fmt.Errorf("invalid key %s", key) - } - resource := tokens[2] - // Verify that the groupVersion is legacyscheme.Registry. - if !legacyscheme.Registry.IsRegisteredVersion(groupVersion) { - return nil, fmt.Errorf("group version %s that has not been registered", groupVersion.String()) - } - - if !resourceConfig.AnyResourcesForVersionEnabled(groupVersion) { - return nil, fmt.Errorf("%v is disabled, you cannot configure its resources individually", groupVersion) - } - - enabled, err := getRuntimeConfigValue(overrides, key, false) - if err != nil { - return nil, err - } - if enabled { - resourceConfig.EnableResources(groupVersion.WithResource(resource)) - } else { - resourceConfig.DisableResources(groupVersion.WithResource(resource)) - } - } - return resourceConfig, nil -} - -func getRuntimeConfigValue(overrides utilflag.ConfigurationMap, apiKey string, defaultValue bool) (bool, error) { - flagValue, ok := overrides[apiKey] - if ok { - if flagValue == "" { - return true, nil - } - boolValue, err := strconv.ParseBool(flagValue) - if err != nil { - return false, fmt.Errorf("invalid value of %s: %s, err: %v", apiKey, flagValue, err) - } - return boolValue, nil - } - return defaultValue, nil -} diff --git a/pkg/kubeapiserver/default_storage_factory_builder_test.go b/pkg/kubeapiserver/default_storage_factory_builder_test.go deleted file mode 100644 index b5c9b4d8867..00000000000 --- a/pkg/kubeapiserver/default_storage_factory_builder_test.go +++ /dev/null @@ -1,210 +0,0 @@ -/* -Copyright 2016 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 kubeapiserver - -import ( - "reflect" - "testing" - - apiv1 "k8s.io/api/core/v1" - extensionsapiv1beta1 "k8s.io/api/extensions/v1beta1" - "k8s.io/apimachinery/pkg/runtime/schema" - serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api/legacyscheme" - _ "k8s.io/kubernetes/pkg/apis/core/install" - _ "k8s.io/kubernetes/pkg/apis/extensions/install" -) - -func TestParseRuntimeConfig(t *testing.T) { - extensionsGroupVersion := extensionsapiv1beta1.SchemeGroupVersion - apiv1GroupVersion := apiv1.SchemeGroupVersion - testCases := []struct { - runtimeConfig map[string]string - defaultResourceConfig func() *serverstorage.ResourceConfig - expectedAPIConfig func() *serverstorage.ResourceConfig - err bool - }{ - { - // everything default value. - runtimeConfig: map[string]string{}, - defaultResourceConfig: func() *serverstorage.ResourceConfig { - return serverstorage.NewResourceConfig() - }, - expectedAPIConfig: func() *serverstorage.ResourceConfig { - return serverstorage.NewResourceConfig() - }, - err: false, - }, - { - // no runtimeConfig override. - runtimeConfig: map[string]string{}, - defaultResourceConfig: func() *serverstorage.ResourceConfig { - config := serverstorage.NewResourceConfig() - config.DisableVersions(extensionsapiv1beta1.SchemeGroupVersion) - return config - }, - expectedAPIConfig: func() *serverstorage.ResourceConfig { - config := serverstorage.NewResourceConfig() - config.DisableVersions(extensionsapiv1beta1.SchemeGroupVersion) - return config - }, - err: false, - }, - { - // version enabled by runtimeConfig override. - runtimeConfig: map[string]string{ - "extensions/v1beta1": "", - }, - defaultResourceConfig: func() *serverstorage.ResourceConfig { - config := serverstorage.NewResourceConfig() - config.DisableVersions(extensionsapiv1beta1.SchemeGroupVersion) - return config - }, - expectedAPIConfig: func() *serverstorage.ResourceConfig { - config := serverstorage.NewResourceConfig() - config.EnableVersions(extensionsapiv1beta1.SchemeGroupVersion) - return config - }, - err: false, - }, - { - // disable resource - runtimeConfig: map[string]string{ - "api/v1/pods": "false", - }, - defaultResourceConfig: func() *serverstorage.ResourceConfig { - config := serverstorage.NewResourceConfig() - config.EnableVersions(apiv1GroupVersion) - return config - }, - expectedAPIConfig: func() *serverstorage.ResourceConfig { - config := serverstorage.NewResourceConfig() - config.EnableVersions(apiv1GroupVersion) - config.DisableResources(apiv1GroupVersion.WithResource("pods")) - return config - }, - err: false, - }, - { - // Disable v1. - runtimeConfig: map[string]string{ - "api/v1": "false", - }, - defaultResourceConfig: func() *serverstorage.ResourceConfig { - return serverstorage.NewResourceConfig() - }, - expectedAPIConfig: func() *serverstorage.ResourceConfig { - config := serverstorage.NewResourceConfig() - config.DisableVersions(apiv1GroupVersion) - return config - }, - err: false, - }, - { - // Enable deployments and disable daemonsets. - runtimeConfig: map[string]string{ - "extensions/v1beta1/anything": "true", - "extensions/v1beta1/daemonsets": "false", - }, - defaultResourceConfig: func() *serverstorage.ResourceConfig { - config := serverstorage.NewResourceConfig() - config.EnableVersions(extensionsGroupVersion) - return config - }, - - expectedAPIConfig: func() *serverstorage.ResourceConfig { - config := serverstorage.NewResourceConfig() - config.EnableVersions(extensionsGroupVersion) - config.DisableResources(extensionsGroupVersion.WithResource("daemonsets")) - config.EnableResources(extensionsGroupVersion.WithResource("anything")) - return config - }, - err: false, - }, - { - // invalid runtime config - runtimeConfig: map[string]string{ - "invalidgroup/version": "false", - }, - defaultResourceConfig: func() *serverstorage.ResourceConfig { - return serverstorage.NewResourceConfig() - }, - expectedAPIConfig: func() *serverstorage.ResourceConfig { - return serverstorage.NewResourceConfig() - }, - err: true, - }, - { - // cannot disable individual resource when version is not enabled. - runtimeConfig: map[string]string{ - "api/v1/pods": "false", - }, - defaultResourceConfig: func() *serverstorage.ResourceConfig { - return serverstorage.NewResourceConfig() - }, - expectedAPIConfig: func() *serverstorage.ResourceConfig { - config := serverstorage.NewResourceConfig() - config.DisableResources(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}) - return config - }, - err: true, - }, - { - // enable all - runtimeConfig: map[string]string{ - "api/all": "true", - }, - defaultResourceConfig: func() *serverstorage.ResourceConfig { - return serverstorage.NewResourceConfig() - }, - expectedAPIConfig: func() *serverstorage.ResourceConfig { - config := serverstorage.NewResourceConfig() - config.EnableVersions(legacyscheme.Registry.RegisteredGroupVersions()...) - return config - }, - err: false, - }, - { - // disable all - runtimeConfig: map[string]string{ - "api/all": "false", - }, - defaultResourceConfig: func() *serverstorage.ResourceConfig { - return serverstorage.NewResourceConfig() - }, - expectedAPIConfig: func() *serverstorage.ResourceConfig { - config := serverstorage.NewResourceConfig() - config.DisableVersions(legacyscheme.Registry.RegisteredGroupVersions()...) - return config - }, - err: false, - }, - } - for _, test := range testCases { - actualDisablers, err := mergeAPIResourceConfigs(test.defaultResourceConfig(), test.runtimeConfig) - if err == nil && test.err { - t.Fatalf("expected error for test: %v", test) - } else if err != nil && !test.err { - t.Fatalf("unexpected error: %s, for test: %v", err, test) - } - - expectedConfig := test.expectedAPIConfig() - if err == nil && !reflect.DeepEqual(actualDisablers, expectedConfig) { - t.Fatalf("%v: unexpected apiResourceDisablers. Actual: %v\n expected: %v", test.runtimeConfig, actualDisablers, expectedConfig) - } - } -} diff --git a/pkg/kubeapiserver/options/api_enablement.go b/pkg/kubeapiserver/options/api_enablement.go deleted file mode 100644 index a5d7f625de0..00000000000 --- a/pkg/kubeapiserver/options/api_enablement.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -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 options - -import ( - "github.com/spf13/pflag" - - utilflag "k8s.io/apiserver/pkg/util/flag" -) - -// APIEnablementOptions contains the options for which resources to turn on and off. -// Given small aggregated API servers, this option isn't required for "normal" API servers -type APIEnablementOptions struct { - RuntimeConfig utilflag.ConfigurationMap -} - -func NewAPIEnablementOptions() *APIEnablementOptions { - return &APIEnablementOptions{ - RuntimeConfig: make(utilflag.ConfigurationMap), - } -} - -// AddFlags adds flags for a specific APIServer to the specified FlagSet -func (s *APIEnablementOptions) AddFlags(fs *pflag.FlagSet) { - fs.Var(&s.RuntimeConfig, "runtime-config", ""+ - "A set of key=value pairs that describe runtime configuration that may be passed "+ - "to apiserver. / (or for the core group) key can be used to "+ - "turn on/off specific api versions. // (or / "+ - "for the core group) can be used to turn on/off specific resources. api/all and "+ - "api/legacy are special keys to control all and legacy api versions respectively.") -} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go index b8af7d55595..02fefaf564e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go @@ -35,6 +35,7 @@ import ( genericregistry "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" + serverstorage "k8s.io/apiserver/pkg/server/storage" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install" @@ -53,7 +54,7 @@ import ( var ( groupFactoryRegistry = make(announced.APIGroupFactoryRegistry) - registry = registered.NewOrDie("") + Registry = registered.NewOrDie("") Scheme = runtime.NewScheme() Codecs = serializer.NewCodecFactory(Scheme) @@ -70,7 +71,7 @@ var ( ) func init() { - install.Install(groupFactoryRegistry, registry, Scheme) + install.Install(groupFactoryRegistry, Registry, Scheme) // we need to add the options to empty v1 metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Group: "", Version: "v1"}) @@ -131,13 +132,19 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) GenericAPIServer: genericServer, } - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiextensions.GroupName, registry, Scheme, metav1.ParameterCodec, Codecs) - apiGroupInfo.GroupMeta.GroupVersion = v1beta1.SchemeGroupVersion - customResourceDefintionStorage := customresourcedefinition.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter) - v1beta1storage := map[string]rest.Storage{} - v1beta1storage["customresourcedefinitions"] = customResourceDefintionStorage - v1beta1storage["customresourcedefinitions/status"] = customresourcedefinition.NewStatusREST(Scheme, customResourceDefintionStorage) - apiGroupInfo.VersionedResourcesStorageMap["v1beta1"] = v1beta1storage + apiResourceConfig := c.GenericConfig.MergedResourceConfig + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiextensions.GroupName, Registry, Scheme, metav1.ParameterCodec, Codecs) + if apiResourceConfig.AnyResourcesForVersionEnabled(v1beta1.SchemeGroupVersion) { + apiGroupInfo.GroupMeta.GroupVersion = v1beta1.SchemeGroupVersion + storage := map[string]rest.Storage{} + version := v1beta1.SchemeGroupVersion + if apiResourceConfig.ResourceEnabled(version.WithResource("customresourcedefinitions")) { + customResourceDefintionStorage := customresourcedefinition.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter) + storage["customresourcedefinitions"] = customResourceDefintionStorage + storage["customresourcedefinitions/status"] = customresourcedefinition.NewStatusREST(Scheme, customResourceDefintionStorage) + } + apiGroupInfo.VersionedResourcesStorageMap["v1beta1"] = storage + } if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil { return nil, err @@ -211,3 +218,13 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) return s, nil } + +func DefaultAPIResourceConfigSource() *serverstorage.ResourceConfig { + ret := serverstorage.NewResourceConfig() + // NOTE: GroupVersions listed here will be enabled by default. Don't put alpha versions in the list. + ret.EnableVersions( + v1beta1.SchemeGroupVersion, + ) + + return ret +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/cmd/server/start.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/cmd/server/start.go index d2096412bfd..df3cf3e60bf 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/cmd/server/start.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/cmd/server/start.go @@ -36,6 +36,7 @@ const defaultEtcdPathPrefix = "/registry/apiextensions.kubernetes.io" type CustomResourceDefinitionsServerOptions struct { RecommendedOptions *genericoptions.RecommendedOptions + APIEnablement *genericoptions.APIEnablementOptions StdOut io.Writer StdErr io.Writer @@ -44,6 +45,7 @@ type CustomResourceDefinitionsServerOptions struct { func NewCustomResourceDefinitionsServerOptions(out, errOut io.Writer) *CustomResourceDefinitionsServerOptions { o := &CustomResourceDefinitionsServerOptions{ RecommendedOptions: genericoptions.NewRecommendedOptions(defaultEtcdPathPrefix, apiserver.Codecs.LegacyCodec(v1beta1.SchemeGroupVersion)), + APIEnablement: genericoptions.NewAPIEnablementOptions(), StdOut: out, StdErr: errOut, @@ -77,13 +79,14 @@ func NewCommandStartCustomResourceDefinitionsServer(out, errOut io.Writer, stopC flags := cmd.Flags() o.RecommendedOptions.AddFlags(flags) - + o.APIEnablement.AddFlags(flags) return cmd } func (o CustomResourceDefinitionsServerOptions) Validate(args []string) error { errors := []error{} errors = append(errors, o.RecommendedOptions.Validate()...) + errors = append(errors, o.APIEnablement.Validate(apiserver.Registry)...) return utilerrors.NewAggregate(errors) } @@ -101,6 +104,9 @@ func (o CustomResourceDefinitionsServerOptions) Config() (*apiserver.Config, err if err := o.RecommendedOptions.ApplyTo(serverConfig, apiserver.Scheme); err != nil { return nil, err } + if err := o.APIEnablement.ApplyTo(&serverConfig.Config, apiserver.DefaultAPIResourceConfigSource(), apiserver.Registry); err != nil { + return nil, err + } config := &apiserver.Config{ GenericConfig: serverConfig, diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/start.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/start.go index 81314842be3..32dd6d9659e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/start.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/start.go @@ -62,6 +62,9 @@ func DefaultServerConfig() (*extensionsapiserver.Config, error) { if err := options.RecommendedOptions.ApplyTo(genericConfig, nil); err != nil { return nil, err } + if err := options.APIEnablement.ApplyTo(&genericConfig.Config, extensionsapiserver.DefaultAPIResourceConfigSource(), extensionsapiserver.Registry); err != nil { + return nil, err + } customResourceDefinitionRESTOptionsGetter := extensionsapiserver.CRDRESTOptionsGetter{ StorageConfig: options.RecommendedOptions.Etcd.StorageConfig, diff --git a/staging/src/k8s.io/apiserver/pkg/server/config.go b/staging/src/k8s.io/apiserver/pkg/server/config.go index 35acaa8125e..8ad2ddb5a27 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/config.go +++ b/staging/src/k8s.io/apiserver/pkg/server/config.go @@ -57,6 +57,7 @@ import ( genericfilters "k8s.io/apiserver/pkg/server/filters" "k8s.io/apiserver/pkg/server/healthz" "k8s.io/apiserver/pkg/server/routes" + serverstore "k8s.io/apiserver/pkg/server/storage" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" restclient "k8s.io/client-go/rest" @@ -175,6 +176,11 @@ type Config struct { // if the client requests it via Accept-Encoding EnableAPIResponseCompression bool + // MergedResourceConfig indicates which groupVersion enabled and its resources enabled/disabled. + // This is composed of genericapiserver defaultAPIResourceConfig and those parsed from flags. + // If not specify any in flags, then genericapiserver will only enable defaultAPIResourceConfig. + MergedResourceConfig *serverstore.ResourceConfig + //=========================================================================== // values below here are targets for removal //=========================================================================== diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement.go b/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement.go new file mode 100644 index 00000000000..3901511f677 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement.go @@ -0,0 +1,111 @@ +/* +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 options + +import ( + "fmt" + "strings" + + "github.com/spf13/pflag" + + "k8s.io/apiserver/pkg/server" + "k8s.io/apiserver/pkg/server/resourceconfig" + serverstore "k8s.io/apiserver/pkg/server/storage" + utilflag "k8s.io/apiserver/pkg/util/flag" +) + +// APIEnablementOptions contains the options for which resources to turn on and off. +// Given small aggregated API servers, this option isn't required for "normal" API servers +type APIEnablementOptions struct { + RuntimeConfig utilflag.ConfigurationMap +} + +func NewAPIEnablementOptions() *APIEnablementOptions { + return &APIEnablementOptions{ + RuntimeConfig: make(utilflag.ConfigurationMap), + } +} + +// AddFlags adds flags for a specific APIServer to the specified FlagSet +func (s *APIEnablementOptions) AddFlags(fs *pflag.FlagSet) { + fs.Var(&s.RuntimeConfig, "runtime-config", ""+ + "A set of key=value pairs that describe runtime configuration that may be passed "+ + "to apiserver. / (or for the core group) key can be used to "+ + "turn on/off specific api versions. api/all is special key to control all api versions, "+ + "be careful setting it false, unless you know what you do. api/legacy is deprecated, "+ + "we will remove it in the future, so stop using it.") +} + +// Validate validates RuntimeConfig with a list of registries. +// Usually this list only has one element, the apiserver registry of the process. +// But in the advanced (and usually not recommended) case of delegated apiservers there can be more. +// Validate will filter out the known groups of each registry. +// If anything is left over after that, an error is returned. +func (s *APIEnablementOptions) Validate(registries ...GroupRegisty) []error { + if s == nil { + return nil + } + + errors := []error{} + if s.RuntimeConfig["api/all"] == "false" && len(s.RuntimeConfig) == 1 { + // Do not allow only set api/all=false, in such case apiserver startup has no meaning. + return append(errors, fmt.Errorf("invliad key with only api/all=false")) + } + + groups, err := resourceconfig.ParseGroups(s.RuntimeConfig) + if err != nil { + return append(errors, err) + } + + for _, registry := range registries { + // filter out known groups + groups = unknownGroups(groups, registry) + } + if len(groups) != 0 { + errors = append(errors, fmt.Errorf("unknown api groups %s", strings.Join(groups, ","))) + } + + return errors +} + +// ApplyTo override MergedResourceConfig with defaults and registry +func (s *APIEnablementOptions) ApplyTo(c *server.Config, defaultResourceConfig *serverstore.ResourceConfig, registry resourceconfig.GroupVersionRegistry) error { + if s == nil { + return nil + } + + mergedResourceConfig, err := resourceconfig.MergeAPIResourceConfigs(defaultResourceConfig, s.RuntimeConfig, registry) + c.MergedResourceConfig = mergedResourceConfig + + return err +} + +func unknownGroups(groups []string, registry GroupRegisty) []string { + unknownGroups := []string{} + for _, group := range groups { + if !registry.IsRegistered(group) { + unknownGroups = append(unknownGroups, group) + } + } + return unknownGroups +} + +// GroupRegisty provides a method to check whether given group is registered. +type GroupRegisty interface { + // IsRegistered returns true if given group is registered. + IsRegistered(group string) bool +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/resourceconfig/doc.go b/staging/src/k8s.io/apiserver/pkg/server/resourceconfig/doc.go new file mode 100644 index 00000000000..0dae21535dc --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/resourceconfig/doc.go @@ -0,0 +1,18 @@ +/* +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 resourceconfig contains the resource config related helper functions. +package resourceconfig // import "k8s.io/apiserver/pkg/server/resourceconfig" diff --git a/staging/src/k8s.io/apiserver/pkg/server/resourceconfig/helpers.go b/staging/src/k8s.io/apiserver/pkg/server/resourceconfig/helpers.go new file mode 100644 index 00000000000..3ac79b74a60 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/resourceconfig/helpers.go @@ -0,0 +1,164 @@ +/* +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 resourceconfig + +import ( + "fmt" + "strconv" + "strings" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + serverstore "k8s.io/apiserver/pkg/server/storage" + utilflag "k8s.io/apiserver/pkg/util/flag" +) + +// GroupVersionRegistry provides access to registered group versions. +type GroupVersionRegistry interface { + // IsRegistered returns true if given group is registered. + IsRegistered(group string) bool + // IsRegisteredVersion returns true if given version is registered. + IsRegisteredVersion(v schema.GroupVersion) bool + // RegisteredGroupVersions returns all registered group versions. + RegisteredGroupVersions() []schema.GroupVersion +} + +// MergeResourceEncodingConfigs merges the given defaultResourceConfig with specific GroupVersionResource overrides. +func MergeResourceEncodingConfigs( + defaultResourceEncoding *serverstore.DefaultResourceEncodingConfig, + resourceEncodingOverrides []schema.GroupVersionResource, +) *serverstore.DefaultResourceEncodingConfig { + resourceEncodingConfig := defaultResourceEncoding + for _, gvr := range resourceEncodingOverrides { + resourceEncodingConfig.SetResourceEncoding(gvr.GroupResource(), gvr.GroupVersion(), + schema.GroupVersion{Group: gvr.Group, Version: runtime.APIVersionInternal}) + } + return resourceEncodingConfig +} + +// MergeGroupEncodingConfigs merges the given defaultResourceConfig with specific GroupVersion overrides. +func MergeGroupEncodingConfigs( + defaultResourceEncoding *serverstore.DefaultResourceEncodingConfig, + storageEncodingOverrides map[string]schema.GroupVersion, +) *serverstore.DefaultResourceEncodingConfig { + resourceEncodingConfig := defaultResourceEncoding + for group, storageEncodingVersion := range storageEncodingOverrides { + resourceEncodingConfig.SetVersionEncoding(group, storageEncodingVersion, schema.GroupVersion{Group: group, Version: runtime.APIVersionInternal}) + } + return resourceEncodingConfig +} + +// MergeAPIResourceConfigs merges the given defaultAPIResourceConfig with the given resourceConfigOverrides. +// Exclude the groups not registered in registry, and check if version is +// not registered in group, then it will fail. +func MergeAPIResourceConfigs( + defaultAPIResourceConfig *serverstore.ResourceConfig, + resourceConfigOverrides utilflag.ConfigurationMap, + registry GroupVersionRegistry, +) (*serverstore.ResourceConfig, error) { + resourceConfig := defaultAPIResourceConfig + overrides := resourceConfigOverrides + + // "api/all=false" allows users to selectively enable specific api versions. + allAPIFlagValue, ok := overrides["api/all"] + if ok { + if allAPIFlagValue == "false" { + // Disable all group versions. + resourceConfig.DisableVersions(registry.RegisteredGroupVersions()...) + } else if allAPIFlagValue == "true" { + resourceConfig.EnableVersions(registry.RegisteredGroupVersions()...) + } + } + + // "={true|false} allows users to enable/disable API. + // This takes preference over api/all, if specified. + // Iterate through all group/version overrides specified in runtimeConfig. + for key := range overrides { + // Have already handled them above. Can skip them here. + if key == "api/all" { + continue + } + + tokens := strings.Split(key, "/") + if len(tokens) != 2 { + continue + } + groupVersionString := tokens[0] + "/" + tokens[1] + groupVersion, err := schema.ParseGroupVersion(groupVersionString) + if err != nil { + return nil, fmt.Errorf("invalid key %s", key) + } + + // Exclude group not registered into the registry. + if !registry.IsRegistered(groupVersion.Group) { + continue + } + + // Verify that the groupVersion is registered into registry. + if !registry.IsRegisteredVersion(groupVersion) { + return nil, fmt.Errorf("group version %s that has not been registered", groupVersion.String()) + } + enabled, err := getRuntimeConfigValue(overrides, key, false) + if err != nil { + return nil, err + } + if enabled { + resourceConfig.EnableVersions(groupVersion) + } else { + resourceConfig.DisableVersions(groupVersion) + } + } + + return resourceConfig, nil +} + +func getRuntimeConfigValue(overrides utilflag.ConfigurationMap, apiKey string, defaultValue bool) (bool, error) { + flagValue, ok := overrides[apiKey] + if ok { + if flagValue == "" { + return true, nil + } + boolValue, err := strconv.ParseBool(flagValue) + if err != nil { + return false, fmt.Errorf("invalid value of %s: %s, err: %v", apiKey, flagValue, err) + } + return boolValue, nil + } + return defaultValue, nil +} + +// ParseGroups takes in resourceConfig and returns parsed groups. +func ParseGroups(resourceConfig utilflag.ConfigurationMap) ([]string, error) { + groups := []string{} + for key := range resourceConfig { + if key == "api/all" { + continue + } + tokens := strings.Split(key, "/") + if len(tokens) != 2 && len(tokens) != 3 { + return groups, fmt.Errorf("runtime-config invalid key %s", key) + } + groupVersionString := tokens[0] + "/" + tokens[1] + groupVersion, err := schema.ParseGroupVersion(groupVersionString) + if err != nil { + return nil, fmt.Errorf("runtime-config invalid key %s", key) + } + groups = append(groups, groupVersion.Group) + } + + return groups, nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/resourceconfig/helpers_test.go b/staging/src/k8s.io/apiserver/pkg/server/resourceconfig/helpers_test.go new file mode 100644 index 00000000000..801d784143a --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/resourceconfig/helpers_test.go @@ -0,0 +1,178 @@ +/* +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 resourceconfig + +import ( + "reflect" + "testing" + + apiv1 "k8s.io/api/core/v1" + extensionsapiv1beta1 "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/apimachinery" + "k8s.io/apimachinery/pkg/apimachinery/registered" + "k8s.io/apimachinery/pkg/runtime/schema" + serverstore "k8s.io/apiserver/pkg/server/storage" +) + +func TestParseRuntimeConfig(t *testing.T) { + registry := newFakeRegistry() + apiv1GroupVersion := apiv1.SchemeGroupVersion + testCases := []struct { + runtimeConfig map[string]string + defaultResourceConfig func() *serverstore.ResourceConfig + expectedAPIConfig func() *serverstore.ResourceConfig + err bool + }{ + { + // everything default value. + runtimeConfig: map[string]string{}, + defaultResourceConfig: func() *serverstore.ResourceConfig { + return newFakeAPIResourceConfigSource() + }, + expectedAPIConfig: func() *serverstore.ResourceConfig { + return newFakeAPIResourceConfigSource() + }, + err: false, + }, + { + // no runtimeConfig override. + runtimeConfig: map[string]string{}, + defaultResourceConfig: func() *serverstore.ResourceConfig { + config := newFakeAPIResourceConfigSource() + config.DisableVersions(extensionsapiv1beta1.SchemeGroupVersion) + return config + }, + expectedAPIConfig: func() *serverstore.ResourceConfig { + config := newFakeAPIResourceConfigSource() + config.DisableVersions(extensionsapiv1beta1.SchemeGroupVersion) + return config + }, + err: false, + }, + { + // version enabled by runtimeConfig override. + runtimeConfig: map[string]string{ + "extensions/v1beta1": "", + }, + defaultResourceConfig: func() *serverstore.ResourceConfig { + config := newFakeAPIResourceConfigSource() + return config + }, + expectedAPIConfig: func() *serverstore.ResourceConfig { + config := newFakeAPIResourceConfigSource() + return config + }, + err: false, + }, + { + // Disable v1. + runtimeConfig: map[string]string{ + "/v1": "false", + }, + defaultResourceConfig: func() *serverstore.ResourceConfig { + return newFakeAPIResourceConfigSource() + }, + expectedAPIConfig: func() *serverstore.ResourceConfig { + config := newFakeAPIResourceConfigSource() + config.DisableVersions(apiv1GroupVersion) + return config + }, + err: false, + }, + { + // invalid runtime config + runtimeConfig: map[string]string{ + "invalidgroup/version": "false", + }, + defaultResourceConfig: func() *serverstore.ResourceConfig { + return newFakeAPIResourceConfigSource() + }, + expectedAPIConfig: func() *serverstore.ResourceConfig { + return newFakeAPIResourceConfigSource() + }, + err: false, + }, + { + // enable all + runtimeConfig: map[string]string{ + "api/all": "true", + }, + defaultResourceConfig: func() *serverstore.ResourceConfig { + return newFakeAPIResourceConfigSource() + }, + expectedAPIConfig: func() *serverstore.ResourceConfig { + config := newFakeAPIResourceConfigSource() + config.EnableVersions(registry.RegisteredGroupVersions()...) + return config + }, + err: false, + }, + { + // only enable v1 + runtimeConfig: map[string]string{ + "api/all": "false", + "/v1": "true", + }, + defaultResourceConfig: func() *serverstore.ResourceConfig { + return newFakeAPIResourceConfigSource() + }, + expectedAPIConfig: func() *serverstore.ResourceConfig { + config := newFakeAPIResourceConfigSource() + config.DisableVersions(extensionsapiv1beta1.SchemeGroupVersion) + return config + }, + err: false, + }, + } + for index, test := range testCases { + actualDisablers, err := MergeAPIResourceConfigs(test.defaultResourceConfig(), test.runtimeConfig, registry) + if err == nil && test.err { + t.Fatalf("expected error for test case: %v", index) + } else if err != nil && !test.err { + t.Fatalf("unexpected error: %s, for test: %v", err, test) + } + + expectedConfig := test.expectedAPIConfig() + if err == nil && !reflect.DeepEqual(actualDisablers, expectedConfig) { + t.Fatalf("%v: unexpected apiResourceDisablers. Actual: %v\n expected: %v", test.runtimeConfig, actualDisablers, expectedConfig) + } + } +} + +func newFakeAPIResourceConfigSource() *serverstore.ResourceConfig { + ret := serverstore.NewResourceConfig() + // NOTE: GroupVersions listed here will be enabled by default. Don't put alpha versions in the list. + ret.EnableVersions( + apiv1.SchemeGroupVersion, + extensionsapiv1beta1.SchemeGroupVersion, + ) + + return ret +} + +func newFakeRegistry() *registered.APIRegistrationManager { + registry := registered.NewOrDie("") + + registry.RegisterGroup(apimachinery.GroupMeta{ + GroupVersion: apiv1.SchemeGroupVersion, + }) + registry.RegisterGroup(apimachinery.GroupMeta{ + GroupVersion: extensionsapiv1beta1.SchemeGroupVersion, + }) + registry.RegisterVersions([]schema.GroupVersion{apiv1.SchemeGroupVersion, extensionsapiv1beta1.SchemeGroupVersion}) + return registry +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/storage/storage_factory.go b/staging/src/k8s.io/apiserver/pkg/server/storage/storage_factory.go index 429e14d5ca2..9e56eedc234 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/storage/storage_factory.go +++ b/staging/src/k8s.io/apiserver/pkg/server/storage/storage_factory.go @@ -151,7 +151,14 @@ var _ StorageFactory = &DefaultStorageFactory{} const AllResources = "*" -func NewDefaultStorageFactory(config storagebackend.Config, defaultMediaType string, defaultSerializer runtime.StorageSerializer, resourceEncodingConfig ResourceEncodingConfig, resourceConfig APIResourceConfigSource, specialDefaultResourcePrefixes map[schema.GroupResource]string) *DefaultStorageFactory { +func NewDefaultStorageFactory( + config storagebackend.Config, + defaultMediaType string, + defaultSerializer runtime.StorageSerializer, + resourceEncodingConfig ResourceEncodingConfig, + resourceConfig APIResourceConfigSource, + specialDefaultResourcePrefixes map[schema.GroupResource]string, +) *DefaultStorageFactory { config.Paging = utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking) if len(defaultMediaType) == 0 { defaultMediaType = runtime.ContentTypeJSON diff --git a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go index 2c805bc1f12..77338d13701 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go @@ -30,6 +30,7 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" + serverstorage "k8s.io/apiserver/pkg/server/storage" "k8s.io/client-go/pkg/version" "k8s.io/kube-aggregator/pkg/apis/apiregistration" @@ -45,13 +46,13 @@ import ( var ( groupFactoryRegistry = make(announced.APIGroupFactoryRegistry) - registry = registered.NewOrDie("") + Registry = registered.NewOrDie("") Scheme = runtime.NewScheme() Codecs = serializer.NewCodecFactory(Scheme) ) func init() { - install.Install(groupFactoryRegistry, registry, Scheme) + install.Install(groupFactoryRegistry, Registry, Scheme) // we need to add the options (like ListOptions) to empty v1 metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Group: "", Version: "v1"}) @@ -181,13 +182,19 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg serviceResolver: c.ExtraConfig.ServiceResolver, } - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiregistration.GroupName, registry, Scheme, metav1.ParameterCodec, Codecs) - apiGroupInfo.GroupMeta.GroupVersion = v1beta1.SchemeGroupVersion - v1beta1storage := map[string]rest.Storage{} - apiServiceREST := apiservicestorage.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter) - v1beta1storage["apiservices"] = apiServiceREST - v1beta1storage["apiservices/status"] = apiservicestorage.NewStatusREST(Scheme, apiServiceREST) - apiGroupInfo.VersionedResourcesStorageMap["v1beta1"] = v1beta1storage + apiResourceConfig := c.GenericConfig.MergedResourceConfig + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiregistration.GroupName, Registry, Scheme, metav1.ParameterCodec, Codecs) + if apiResourceConfig.AnyResourcesForVersionEnabled(v1beta1.SchemeGroupVersion) { + apiGroupInfo.GroupMeta.GroupVersion = v1beta1.SchemeGroupVersion + storage := map[string]rest.Storage{} + version := v1beta1.SchemeGroupVersion + if apiResourceConfig.ResourceEnabled(version.WithResource("customresourcedefinitions")) { + apiServiceREST := apiservicestorage.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter) + storage["apiservices"] = apiServiceREST + storage["apiservices/status"] = apiservicestorage.NewStatusREST(Scheme, apiServiceREST) + } + apiGroupInfo.VersionedResourcesStorageMap["v1beta1"] = storage + } if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil { return nil, err @@ -330,3 +337,13 @@ func (s *APIAggregator) RemoveAPIService(apiServiceName string) { // TODO unregister group level discovery when there are no more versions for the group // We don't need this right away because the handler properly delegates when no versions are present } + +func DefaultAPIResourceConfigSource() *serverstorage.ResourceConfig { + ret := serverstorage.NewResourceConfig() + // NOTE: GroupVersions listed here will be enabled by default. Don't put alpha versions in the list. + ret.EnableVersions( + v1beta1.SchemeGroupVersion, + ) + + return ret +} diff --git a/staging/src/k8s.io/kube-aggregator/pkg/cmd/server/start.go b/staging/src/k8s.io/kube-aggregator/pkg/cmd/server/start.go index 1b1a652febe..83c83aa3bf8 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/cmd/server/start.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/cmd/server/start.go @@ -37,6 +37,7 @@ const defaultEtcdPathPrefix = "/registry/kube-aggregator.kubernetes.io/" type AggregatorOptions struct { RecommendedOptions *genericoptions.RecommendedOptions + APIEnablement *genericoptions.APIEnablementOptions // ProxyClientCert/Key are the client cert used to identify this proxy. Backing APIServices use // this to confirm the proxy's identity @@ -75,6 +76,7 @@ func NewCommandStartAggregator(out, err io.Writer, stopCh <-chan struct{}) *cobr // AddFlags is necessary because hyperkube doesn't work using cobra, so we have to have different registration and execution paths func (o *AggregatorOptions) AddFlags(fs *pflag.FlagSet) { o.RecommendedOptions.AddFlags(fs) + o.APIEnablement.AddFlags(fs) fs.StringVar(&o.ProxyClientCertFile, "proxy-client-cert-file", o.ProxyClientCertFile, "client certificate used identify the proxy to the API server") fs.StringVar(&o.ProxyClientKeyFile, "proxy-client-key-file", o.ProxyClientKeyFile, "client certificate key used identify the proxy to the API server") } @@ -83,6 +85,7 @@ func (o *AggregatorOptions) AddFlags(fs *pflag.FlagSet) { func NewDefaultOptions(out, err io.Writer) *AggregatorOptions { o := &AggregatorOptions{ RecommendedOptions: genericoptions.NewRecommendedOptions(defaultEtcdPathPrefix, apiserver.Codecs.LegacyCodec(v1beta1.SchemeGroupVersion)), + APIEnablement: genericoptions.NewAPIEnablementOptions(), StdOut: out, StdErr: err, @@ -94,6 +97,7 @@ func NewDefaultOptions(out, err io.Writer) *AggregatorOptions { func (o AggregatorOptions) Validate(args []string) error { errors := []error{} errors = append(errors, o.RecommendedOptions.Validate()...) + errors = append(errors, o.APIEnablement.Validate(apiserver.Registry)...) return utilerrors.NewAggregate(errors) } @@ -112,6 +116,9 @@ func (o AggregatorOptions) RunAggregator(stopCh <-chan struct{}) error { if err := o.RecommendedOptions.ApplyTo(serverConfig, apiserver.Scheme); err != nil { return err } + if err := o.APIEnablement.ApplyTo(&serverConfig.Config, apiserver.DefaultAPIResourceConfigSource(), apiserver.Registry); err != nil { + return err + } serverConfig.LongRunningFunc = filters.BasicLongRunningRequestCheck( sets.NewString("watch", "proxy"), sets.NewString("attach", "exec", "proxy", "log", "portforward"),