diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index 0cd54e8a4ee..0887d9124ba 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -389,6 +389,11 @@ func buildGenericConfig( getOpenAPIDefinitions := openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(generatedopenapi.GetOpenAPIDefinitions) genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(getOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme, extensionsapiserver.Scheme, aggregatorscheme.Scheme)) genericConfig.OpenAPIConfig.Info.Title = "Kubernetes" + if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.OpenAPIV3) { + genericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(getOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme, extensionsapiserver.Scheme, aggregatorscheme.Scheme)) + genericConfig.OpenAPIV3Config.Info.Title = "Kubernetes" + } + genericConfig.LongRunningFunc = filters.BasicLongRunningRequestCheck( sets.NewString("watch", "proxy"), sets.NewString("attach", "exec", "proxy", "log", "portforward"), @@ -436,7 +441,7 @@ func buildGenericConfig( versionedInformers = clientgoinformers.NewSharedInformerFactory(clientgoExternalClient, 10*time.Minute) // Authentication.ApplyTo requires already applied OpenAPIConfig and EgressSelector if present - if lastErr = s.Authentication.ApplyTo(&genericConfig.Authentication, genericConfig.SecureServing, genericConfig.EgressSelector, genericConfig.OpenAPIConfig, clientgoExternalClient, versionedInformers); lastErr != nil { + if lastErr = s.Authentication.ApplyTo(&genericConfig.Authentication, genericConfig.SecureServing, genericConfig.EgressSelector, genericConfig.OpenAPIConfig, genericConfig.OpenAPIV3Config, clientgoExternalClient, versionedInformers); lastErr != nil { return } diff --git a/pkg/kubeapiserver/options/authentication.go b/pkg/kubeapiserver/options/authentication.go index 0ecbc5c533c..9fc64598746 100644 --- a/pkg/kubeapiserver/options/authentication.go +++ b/pkg/kubeapiserver/options/authentication.go @@ -454,7 +454,7 @@ func (o *BuiltInAuthenticationOptions) ToAuthenticationConfig() (kubeauthenticat } // ApplyTo requires already applied OpenAPIConfig and EgressSelector if present. -func (o *BuiltInAuthenticationOptions) ApplyTo(authInfo *genericapiserver.AuthenticationInfo, secureServing *genericapiserver.SecureServingInfo, egressSelector *egressselector.EgressSelector, openAPIConfig *openapicommon.Config, extclient kubernetes.Interface, versionedInformer informers.SharedInformerFactory) error { +func (o *BuiltInAuthenticationOptions) ApplyTo(authInfo *genericapiserver.AuthenticationInfo, secureServing *genericapiserver.SecureServingInfo, egressSelector *egressselector.EgressSelector, openAPIConfig *openapicommon.Config, openAPIV3Config *openapicommon.Config, extclient kubernetes.Interface, versionedInformer informers.SharedInformerFactory) error { if o == nil { return nil } @@ -504,6 +504,9 @@ func (o *BuiltInAuthenticationOptions) ApplyTo(authInfo *genericapiserver.Authen } authInfo.Authenticator, openAPIConfig.SecurityDefinitions, err = authenticatorConfig.New() + if openAPIV3Config != nil { + openAPIV3Config.SecurityDefinitions = openAPIConfig.SecurityDefinitions + } if err != nil { return err } 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 b54a0e92f00..3494930f334 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go @@ -236,9 +236,12 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) // Together they serve the /openapi/v2 endpoint on a generic apiserver. A generic apiserver may // choose to not enable OpenAPI by having null openAPIConfig, and thus OpenAPIVersionedService // and StaticOpenAPISpec are both null. In that case we don't run the CRD OpenAPI controller. - if s.GenericAPIServer.OpenAPIVersionedService != nil && s.GenericAPIServer.StaticOpenAPISpec != nil { - go openapiController.Run(s.GenericAPIServer.StaticOpenAPISpec, s.GenericAPIServer.OpenAPIVersionedService, context.StopCh) - if utilfeature.DefaultFeatureGate.Enabled(features.OpenAPIV3) { + if s.GenericAPIServer.StaticOpenAPISpec != nil { + if s.GenericAPIServer.OpenAPIVersionedService != nil { + go openapiController.Run(s.GenericAPIServer.StaticOpenAPISpec, s.GenericAPIServer.OpenAPIVersionedService, context.StopCh) + } + + if s.GenericAPIServer.OpenAPIV3VersionedService != nil && utilfeature.DefaultFeatureGate.Enabled(features.OpenAPIV3) { go openapiv3Controller.Run(s.GenericAPIServer.OpenAPIV3VersionedService, context.StopCh) } } diff --git a/staging/src/k8s.io/apiserver/pkg/server/config.go b/staging/src/k8s.io/apiserver/pkg/server/config.go index 5c72800a098..f2ea85b82f9 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/config.go +++ b/staging/src/k8s.io/apiserver/pkg/server/config.go @@ -171,6 +171,8 @@ type Config struct { Serializer runtime.NegotiatedSerializer // OpenAPIConfig will be used in generating OpenAPI spec. This is nil by default. Use DefaultOpenAPIConfig for "working" defaults. OpenAPIConfig *openapicommon.Config + // OpenAPIV3Config will be used in generating OpenAPI V3 spec. This is nil by default. Use DefaultOpenAPIV3Config for "working" defaults. + OpenAPIV3Config *openapicommon.Config // SkipOpenAPIInstallation avoids installing the OpenAPI handler if set to true. SkipOpenAPIInstallation bool @@ -382,6 +384,7 @@ func NewRecommendedConfig(codecs serializer.CodecFactory) *RecommendedConfig { } } +// DefaultOpenAPIConfig provides the default OpenAPIConfig used to build the OpenAPI V2 spec func DefaultOpenAPIConfig(getDefinitions openapicommon.GetOpenAPIDefinitions, defNamer *apiopenapi.DefinitionNamer) *openapicommon.Config { return &openapicommon.Config{ ProtocolList: []string{"https"}, @@ -402,6 +405,17 @@ func DefaultOpenAPIConfig(getDefinitions openapicommon.GetOpenAPIDefinitions, de } } +// DefaultOpenAPIV3Config provides the default OpenAPIV3Config used to build the OpenAPI V3 spec +func DefaultOpenAPIV3Config(getDefinitions openapicommon.GetOpenAPIDefinitions, defNamer *apiopenapi.DefinitionNamer) *openapicommon.Config { + defaultConfig := DefaultOpenAPIConfig(getDefinitions, defNamer) + defaultConfig.Definitions = getDefinitions(func(name string) spec.Ref { + defName, _ := defaultConfig.GetDefinitionName(name) + return spec.MustCreateRef("#/components/schemas/" + openapicommon.EscapeJsonPointer(defName)) + }) + + return defaultConfig +} + func (c *AuthenticationInfo) ApplyClientCert(clientCA dynamiccertificates.CAContentProvider, servingInfo *SecureServingInfo) error { if servingInfo == nil { return nil @@ -475,6 +489,45 @@ func (c *Config) AddPostStartHookOrDie(name string, hook PostStartHookFunc) { } } +func completeOpenAPI(config *openapicommon.Config, version *version.Info) { + if config == nil { + return + } + if config.SecurityDefinitions != nil { + // Setup OpenAPI security: all APIs will have the same authentication for now. + config.DefaultSecurity = []map[string][]string{} + keys := []string{} + for k := range *config.SecurityDefinitions { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + config.DefaultSecurity = append(config.DefaultSecurity, map[string][]string{k: {}}) + } + if config.CommonResponses == nil { + config.CommonResponses = map[int]spec.Response{} + } + if _, exists := config.CommonResponses[http.StatusUnauthorized]; !exists { + config.CommonResponses[http.StatusUnauthorized] = spec.Response{ + ResponseProps: spec.ResponseProps{ + Description: "Unauthorized", + }, + } + } + } + // make sure we populate info, and info.version, if not manually set + if config.Info == nil { + config.Info = &spec.Info{} + } + if config.Info.Version == "" { + if version != nil { + config.Info.Version = strings.Split(version.String(), "-")[0] + } else { + config.Info.Version = "unversioned" + } + } +} + // Complete fills in any fields not set that are required to have valid data and can be derived // from other fields. If you're going to `ApplyOptions`, do that first. It's mutating the receiver. func (c *Config) Complete(informers informers.SharedInformerFactory) CompletedConfig { @@ -494,42 +547,9 @@ func (c *Config) Complete(informers informers.SharedInformerFactory) CompletedCo c.ExternalAddress = net.JoinHostPort(c.ExternalAddress, strconv.Itoa(port)) } - if c.OpenAPIConfig != nil { - if c.OpenAPIConfig.SecurityDefinitions != nil { - // Setup OpenAPI security: all APIs will have the same authentication for now. - c.OpenAPIConfig.DefaultSecurity = []map[string][]string{} - keys := []string{} - for k := range *c.OpenAPIConfig.SecurityDefinitions { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - c.OpenAPIConfig.DefaultSecurity = append(c.OpenAPIConfig.DefaultSecurity, map[string][]string{k: {}}) - } - if c.OpenAPIConfig.CommonResponses == nil { - c.OpenAPIConfig.CommonResponses = map[int]spec.Response{} - } - if _, exists := c.OpenAPIConfig.CommonResponses[http.StatusUnauthorized]; !exists { - c.OpenAPIConfig.CommonResponses[http.StatusUnauthorized] = spec.Response{ - ResponseProps: spec.ResponseProps{ - Description: "Unauthorized", - }, - } - } - } + completeOpenAPI(c.OpenAPIConfig, c.Version) + completeOpenAPI(c.OpenAPIV3Config, c.Version) - // make sure we populate info, and info.version, if not manually set - if c.OpenAPIConfig.Info == nil { - c.OpenAPIConfig.Info = &spec.Info{} - } - if c.OpenAPIConfig.Info.Version == "" { - if c.Version != nil { - c.OpenAPIConfig.Info.Version = strings.Split(c.Version.String(), "-")[0] - } else { - c.OpenAPIConfig.Info.Version = "unversioned" - } - } - } if c.DiscoveryAddresses == nil { c.DiscoveryAddresses = discovery.DefaultAddresses{DefaultAddress: c.ExternalAddress} } @@ -606,6 +626,7 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G ExternalAddress: c.ExternalAddress, openAPIConfig: c.OpenAPIConfig, + openAPIV3Config: c.OpenAPIV3Config, skipOpenAPIInstallation: c.SkipOpenAPIInstallation, postStartHooks: map[string]postStartHookEntry{}, diff --git a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go index 7e410b8e573..b8435792856 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go +++ b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go @@ -135,6 +135,9 @@ type GenericAPIServer struct { // Enable swagger and/or OpenAPI if these configs are non-nil. openAPIConfig *openapicommon.Config + // Enable swagger and/or OpenAPI V3 if these configs are non-nil. + openAPIV3Config *openapicommon.Config + // SkipOpenAPIInstallation indicates not to install the OpenAPI handler // during PrepareRun. // Set this to true when the specific API Server has its own OpenAPI handler @@ -351,9 +354,12 @@ func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer { s.OpenAPIVersionedService, s.StaticOpenAPISpec = routes.OpenAPI{ Config: s.openAPIConfig, }.InstallV2(s.Handler.GoRestfulContainer, s.Handler.NonGoRestfulMux) + } + + if s.openAPIV3Config != nil && !s.skipOpenAPIInstallation { if utilfeature.DefaultFeatureGate.Enabled(features.OpenAPIV3) { s.OpenAPIV3VersionedService = routes.OpenAPI{ - Config: s.openAPIConfig, + Config: s.openAPIV3Config, }.InstallV3(s.Handler.GoRestfulContainer, s.Handler.NonGoRestfulMux) } } 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 dbbae01637c..e945fb4899a 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go @@ -144,6 +144,9 @@ type APIAggregator struct { // Enable swagger and/or OpenAPI if these configs are non-nil. openAPIConfig *openapicommon.Config + // Enable OpenAPI V3 if these configs are non-nil + openAPIV3Config *openapicommon.Config + // openAPIAggregationController downloads and merges OpenAPI v2 specs. openAPIAggregationController *openapicontroller.AggregationController @@ -207,6 +210,7 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg APIRegistrationInformers: informerFactory, serviceResolver: c.ExtraConfig.ServiceResolver, openAPIConfig: c.GenericConfig.OpenAPIConfig, + openAPIV3Config: c.GenericConfig.OpenAPIV3Config, egressSelector: c.GenericConfig.EgressSelector, proxyCurrentCertKeyContent: func() (bytes []byte, bytes2 []byte) { return nil, nil }, } @@ -363,9 +367,13 @@ func (s *APIAggregator) PrepareRun() (preparedAPIAggregator, error) { if s.openAPIConfig != nil { s.GenericAPIServer.AddPostStartHookOrDie("apiservice-openapi-controller", func(context genericapiserver.PostStartHookContext) error { go s.openAPIAggregationController.Run(context.StopCh) - if utilfeature.DefaultFeatureGate.Enabled(features.OpenAPIV3) { - go s.openAPIV3AggregationController.Run(context.StopCh) - } + return nil + }) + } + + if s.openAPIV3Config != nil && utilfeature.DefaultFeatureGate.Enabled(features.OpenAPIV3) { + s.GenericAPIServer.AddPostStartHookOrDie("apiservice-openapiv3-controller", func(context genericapiserver.PostStartHookContext) error { + go s.openAPIV3AggregationController.Run(context.StopCh) return nil }) } @@ -385,18 +393,18 @@ func (s *APIAggregator) PrepareRun() (preparedAPIAggregator, error) { return preparedAPIAggregator{}, err } s.openAPIAggregationController = openapicontroller.NewAggregationController(&specDownloader, openAPIAggregator) - if utilfeature.DefaultFeatureGate.Enabled(features.OpenAPIV3) { - specDownloaderV3 := openapiv3aggregator.NewDownloader() - openAPIV3Aggregator, err := openapiv3aggregator.BuildAndRegisterAggregator( - specDownloaderV3, - s.GenericAPIServer.NextDelegate(), - s.GenericAPIServer.Handler.NonGoRestfulMux) - if err != nil { - return preparedAPIAggregator{}, err - } - _ = openAPIV3Aggregator - s.openAPIV3AggregationController = openapiv3controller.NewAggregationController(openAPIV3Aggregator) + } + + if s.openAPIV3Config != nil && utilfeature.DefaultFeatureGate.Enabled(features.OpenAPIV3) { + specDownloaderV3 := openapiv3aggregator.NewDownloader() + openAPIV3Aggregator, err := openapiv3aggregator.BuildAndRegisterAggregator( + specDownloaderV3, + s.GenericAPIServer.NextDelegate(), + s.GenericAPIServer.Handler.NonGoRestfulMux) + if err != nil { + return preparedAPIAggregator{}, err } + s.openAPIV3AggregationController = openapiv3controller.NewAggregationController(openAPIV3Aggregator) } return preparedAPIAggregator{APIAggregator: s, runnable: prepared}, nil diff --git a/staging/src/k8s.io/sample-apiserver/pkg/cmd/server/start.go b/staging/src/k8s.io/sample-apiserver/pkg/cmd/server/start.go index 7683490f9fc..33df43b3cd3 100644 --- a/staging/src/k8s.io/sample-apiserver/pkg/cmd/server/start.go +++ b/staging/src/k8s.io/sample-apiserver/pkg/cmd/server/start.go @@ -139,6 +139,12 @@ func (o *WardleServerOptions) Config() (*apiserver.Config, error) { serverConfig.OpenAPIConfig.Info.Title = "Wardle" serverConfig.OpenAPIConfig.Info.Version = "0.1" + if utilfeature.DefaultFeatureGate.Enabled(features.OpenAPIV3) { + serverConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(sampleopenapi.GetOpenAPIDefinitions, openapi.NewDefinitionNamer(apiserver.Scheme)) + serverConfig.OpenAPIV3Config.Info.Title = "Wardle" + serverConfig.OpenAPIV3Config.Info.Version = "0.1" + } + if err := o.RecommendedOptions.ApplyTo(serverConfig); err != nil { return nil, err } diff --git a/test/integration/apiserver/openapi/openapiv3_test.go b/test/integration/apiserver/openapi/openapiv3_test.go index a3ffa891459..6056219d5fc 100644 --- a/test/integration/apiserver/openapi/openapiv3_test.go +++ b/test/integration/apiserver/openapi/openapiv3_test.go @@ -48,6 +48,7 @@ func TestOpenAPIV3SpecRoundTrip(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.OpenAPIV3, true)() controlPlaneConfig := framework.NewIntegrationTestControlPlaneConfigWithOptions(&framework.ControlPlaneConfigOptions{}) controlPlaneConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig() + controlPlaneConfig.GenericConfig.OpenAPIV3Config = framework.DefaultOpenAPIV3Config() instanceConfig, _, closeFn := framework.RunAnAPIServer(controlPlaneConfig) defer closeFn() paths := []string{ @@ -192,6 +193,7 @@ func TestOpenAPIV3ProtoRoundtrip(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.OpenAPIV3, true)() controlPlaneConfig := framework.NewIntegrationTestControlPlaneConfigWithOptions(&framework.ControlPlaneConfigOptions{}) controlPlaneConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig() + controlPlaneConfig.GenericConfig.OpenAPIV3Config = framework.DefaultOpenAPIV3Config() instanceConfig, _, closeFn := framework.RunAnAPIServer(controlPlaneConfig) defer closeFn() rt, err := restclient.TransportFor(instanceConfig.GenericAPIServer.LoopbackClientConfig) diff --git a/test/integration/framework/controlplane_utils.go b/test/integration/framework/controlplane_utils.go index d0dfcdea032..fc02449a0fa 100644 --- a/test/integration/framework/controlplane_utils.go +++ b/test/integration/framework/controlplane_utils.go @@ -141,6 +141,25 @@ func DefaultOpenAPIConfig() *openapicommon.Config { return openAPIConfig } +// DefaultOpenAPIV3Config returns an openapicommon.Config initialized to default values. +func DefaultOpenAPIV3Config() *openapicommon.Config { + openAPIConfig := genericapiserver.DefaultOpenAPIV3Config(openapi.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme)) + openAPIConfig.Info = &spec.Info{ + InfoProps: spec.InfoProps{ + Title: "Kubernetes", + Version: "unversioned", + }, + } + openAPIConfig.DefaultResponse = &spec.Response{ + ResponseProps: spec.ResponseProps{ + Description: "Default Response.", + }, + } + openAPIConfig.GetDefinitions = utilopenapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(openapi.GetOpenAPIDefinitions) + + return openAPIConfig +} + // startAPIServerOrDie starts a kubernetes API server and an httpserver to handle api requests func startAPIServerOrDie(controlPlaneConfig *controlplane.Config, incomingServer *httptest.Server, apiServerReceiver APIServerReceiver) (*controlplane.Instance, *httptest.Server, CloseFunc) { var m *controlplane.Instance