Separate OpenAPI V2 and V3 Config

This commit is contained in:
Jefftree 2022-03-28 13:18:56 -07:00
parent 9d42879d2c
commit 67d3dbfaae
9 changed files with 128 additions and 55 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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{},

View File

@ -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)
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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