K8s: read resource configs from API Enablement for API Builders (#114329)
This commit is contained in:
@@ -64,10 +64,6 @@ func NewAPIBuilder(providerType string, url *url.URL, insecure bool, caFile stri
|
||||
}
|
||||
|
||||
func RegisterAPIService(apiregistration builder.APIRegistrar, cfg *setting.Cfg) (*APIBuilder, error) {
|
||||
if !cfg.OpenFeature.APIEnabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var staticEvaluator featuremgmt.StaticFlagEvaluator // No static evaluator needed for non-static provider
|
||||
var err error
|
||||
if cfg.OpenFeature.ProviderType == setting.StaticProviderType {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
k8srequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||
"k8s.io/apiserver/pkg/util/openapi"
|
||||
k8sscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
k8stracing "k8s.io/component-base/tracing"
|
||||
@@ -72,13 +73,36 @@ var PathRewriters = []filters.PathRewriter{
|
||||
},
|
||||
}
|
||||
|
||||
// GetDefaultBuildHandlerChainFuncForAggregator is a replica of GetDefaultBuildHandlerChainFunc except it skips custom routes handling
|
||||
func GetDefaultBuildHandlerChainFuncForAggregator() BuildHandlerChainFunc {
|
||||
return func(delegateHandler http.Handler, c *genericapiserver.Config) http.Handler {
|
||||
// filters.WithRequester needs to be after the K8s chain because it depends on the K8s user in context
|
||||
handler := filters.WithRequester(delegateHandler)
|
||||
|
||||
// Call DefaultBuildHandlerChain on the main entrypoint http.Handler
|
||||
// See https://github.com/kubernetes/apiserver/blob/v0.28.0/pkg/server/config.go#L906
|
||||
// DefaultBuildHandlerChain provides many things, notably CORS, HSTS, cache-control, authz and latency tracking
|
||||
handler = genericapiserver.DefaultBuildHandlerChain(handler, c)
|
||||
|
||||
handler = filters.WithAcceptHeader(handler)
|
||||
handler = filters.WithPathRewriters(handler, PathRewriters)
|
||||
handler = k8stracing.WithTracing(handler, c.TracerProvider, "KubernetesAPI")
|
||||
handler = filters.WithExtractJaegerTrace(handler)
|
||||
// Configure filters.WithPanicRecovery to not crash on panic
|
||||
utilruntime.ReallyCrash = false
|
||||
|
||||
return handler
|
||||
}
|
||||
}
|
||||
func GetDefaultBuildHandlerChainFunc(builders []APIGroupBuilder, reg prometheus.Registerer) BuildHandlerChainFunc {
|
||||
return func(delegateHandler http.Handler, c *genericapiserver.Config) http.Handler {
|
||||
requestHandler, err := GetCustomRoutesHandler(
|
||||
delegateHandler,
|
||||
c.LoopbackClientConfig,
|
||||
builders,
|
||||
reg)
|
||||
reg,
|
||||
c.MergedResourceConfig,
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not build the request handler for specified API builders: %s", err.Error()))
|
||||
}
|
||||
@@ -105,6 +129,8 @@ func GetDefaultBuildHandlerChainFunc(builders []APIGroupBuilder, reg prometheus.
|
||||
}
|
||||
}
|
||||
|
||||
// SetupConfig sets up the server config for the API server
|
||||
// specify isAggregator=true, if the chain is being constructed for kube-aggregator
|
||||
func SetupConfig(
|
||||
scheme *runtime.Scheme,
|
||||
serverConfig *genericapiserver.RecommendedConfig,
|
||||
@@ -114,6 +140,7 @@ func SetupConfig(
|
||||
gvs []schema.GroupVersion,
|
||||
additionalOpenAPIDefGetters []common.GetOpenAPIDefinitions,
|
||||
reg prometheus.Registerer,
|
||||
apiResourceConfig *serverstorage.ResourceConfig,
|
||||
) error {
|
||||
serverConfig.AdmissionControl = NewAdmissionFromBuilders(builders)
|
||||
defsGetter := GetOpenAPIDefinitions(builders, additionalOpenAPIDefGetters...)
|
||||
@@ -126,7 +153,7 @@ func SetupConfig(
|
||||
openapinamer.NewDefinitionNamer(scheme, k8sscheme.Scheme))
|
||||
|
||||
// Add the custom routes to service discovery
|
||||
serverConfig.OpenAPIV3Config.PostProcessSpec = getOpenAPIPostProcessor(buildVersion, builders, gvs)
|
||||
serverConfig.OpenAPIV3Config.PostProcessSpec = getOpenAPIPostProcessor(buildVersion, builders, gvs, apiResourceConfig)
|
||||
serverConfig.OpenAPIV3Config.GetOperationIDAndTagsFromRoute = func(r common.Route) (string, []string, error) {
|
||||
meta := r.Metadata()
|
||||
kind := ""
|
||||
@@ -287,6 +314,7 @@ func InstallAPIs(
|
||||
features featuremgmt.FeatureToggles,
|
||||
dualWriterMetrics *grafanarest.DualWriterMetrics,
|
||||
builderMetrics *BuilderMetrics,
|
||||
apiResourceConfig *serverstorage.ResourceConfig,
|
||||
) error {
|
||||
// dual writing is only enabled when the storage type is not legacy.
|
||||
// this is needed to support setting a default RESTOptionsGetter for new APIs that don't
|
||||
@@ -401,34 +429,9 @@ func InstallAPIs(
|
||||
for group, buildersForGroup := range buildersGroupMap {
|
||||
g := genericapiserver.NewDefaultAPIGroupInfo(group, scheme, metav1.ParameterCodec, codecs)
|
||||
for _, b := range buildersForGroup {
|
||||
if err := b.UpdateAPIGroupInfo(&g, APIGroupOptions{
|
||||
Scheme: scheme,
|
||||
OptsGetter: optsGetter,
|
||||
DualWriteBuilder: dualWrite,
|
||||
MetricsRegister: reg,
|
||||
StorageOptsRegister: optsregister,
|
||||
StorageOpts: storageOpts,
|
||||
}); err != nil {
|
||||
if err := installAPIGroupsForBuilder(&g, group, b, apiResourceConfig, scheme, optsGetter, dualWrite, reg, optsregister, storageOpts, features); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(g.PrioritizedVersions) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// if grafanaAPIServerWithExperimentalAPIs is not enabled, remove v0alpha1 resources unless explicitly allowed
|
||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
|
||||
if resources, ok := g.VersionedResourcesStorageMap["v0alpha1"]; ok {
|
||||
for name := range resources {
|
||||
if !allowRegisteringResourceByInfo(b.AllowedV0Alpha1Resources(), name) {
|
||||
delete(resources, name)
|
||||
}
|
||||
}
|
||||
if len(resources) == 0 {
|
||||
delete(g.VersionedResourcesStorageMap, "v0alpha1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skip installing the group if there are no resources left after filtering
|
||||
@@ -445,6 +448,53 @@ func InstallAPIs(
|
||||
return nil
|
||||
}
|
||||
|
||||
func installAPIGroupsForBuilder(g *genericapiserver.APIGroupInfo, group string, b APIGroupBuilder, apiResourceConfig *serverstorage.ResourceConfig, scheme *runtime.Scheme,
|
||||
optsGetter generic.RESTOptionsGetter, dualWrite grafanarest.DualWriteBuilder, reg prometheus.Registerer, optsregister apistore.StorageOptionsRegister,
|
||||
storageOpts *options.StorageOptions, features featuremgmt.FeatureToggles) error {
|
||||
if err := b.UpdateAPIGroupInfo(g, APIGroupOptions{
|
||||
Scheme: scheme,
|
||||
OptsGetter: optsGetter,
|
||||
DualWriteBuilder: dualWrite,
|
||||
MetricsRegister: reg,
|
||||
StorageOptsRegister: optsregister,
|
||||
StorageOpts: storageOpts,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(g.PrioritizedVersions) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// filter out api groups that are disabled in APIEnablementOptions
|
||||
for version := range g.VersionedResourcesStorageMap {
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: group,
|
||||
Version: version,
|
||||
}
|
||||
if apiResourceConfig != nil && !apiResourceConfig.ResourceEnabled(gvr) {
|
||||
klog.InfoS("Skipping storage for disabled resource", "gvr", gvr.String())
|
||||
delete(g.VersionedResourcesStorageMap, version)
|
||||
}
|
||||
}
|
||||
|
||||
// if grafanaAPIServerWithExperimentalAPIs is not enabled, remove v0alpha1 resources unless explicitly allowed
|
||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
|
||||
if resources, ok := g.VersionedResourcesStorageMap["v0alpha1"]; ok {
|
||||
for name := range resources {
|
||||
if !allowRegisteringResourceByInfo(b.AllowedV0Alpha1Resources(), name) {
|
||||
delete(resources, name)
|
||||
}
|
||||
}
|
||||
if len(resources) == 0 {
|
||||
delete(g.VersionedResourcesStorageMap, "v0alpha1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPostStartHooks adds post start hooks to a generic API server config
|
||||
func AddPostStartHooks(
|
||||
config *genericapiserver.RecommendedConfig,
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||
"k8s.io/klog/v2"
|
||||
openapi "k8s.io/kube-openapi/pkg/common"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
spec "k8s.io/kube-openapi/pkg/validation/spec"
|
||||
@@ -76,6 +78,7 @@ func addBuilderRoutes(
|
||||
targetGroupVersion schema.GroupVersion,
|
||||
openAPISpec *spec3.OpenAPI,
|
||||
apiGroupBuilders []APIGroupBuilder,
|
||||
apiResourceConfig *serverstorage.ResourceConfig,
|
||||
) (*spec3.OpenAPI, error) {
|
||||
for _, apiGroupBuilder := range apiGroupBuilders {
|
||||
// Optionally include raw http handlers for all builders
|
||||
@@ -107,12 +110,24 @@ func addBuilderRoutes(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filter out api groups that are disabled in APIEnablementOptions
|
||||
for path := range openAPISpec.Paths.Paths {
|
||||
if strings.HasPrefix(path, "/apis/"+targetGroupVersion.String()+"/") {
|
||||
gv := targetGroupVersion.WithResource("")
|
||||
if apiResourceConfig != nil && !apiResourceConfig.ResourceEnabled(gv) {
|
||||
klog.InfoS("removing openapi routes for disabled resource", "gv", gv.String())
|
||||
delete(openAPISpec.Paths.Paths, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return openAPISpec, nil
|
||||
}
|
||||
|
||||
// Modify the OpenAPI spec to include the additional routes.
|
||||
// nolint:gocyclo
|
||||
func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []schema.GroupVersion) func(*spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
||||
func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []schema.GroupVersion, apiResourceConfig *serverstorage.ResourceConfig) func(*spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
||||
return func(s *spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
||||
if s.Paths == nil {
|
||||
return s, nil
|
||||
@@ -227,7 +242,7 @@ func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []s
|
||||
}
|
||||
}
|
||||
}
|
||||
return addBuilderRoutes(gv, ©, builders)
|
||||
return addBuilderRoutes(gv, ©, builders, apiResourceConfig)
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
klog "k8s.io/klog/v2"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
)
|
||||
|
||||
@@ -14,7 +16,7 @@ type requestHandler struct {
|
||||
router *mux.Router
|
||||
}
|
||||
|
||||
func GetCustomRoutesHandler(delegateHandler http.Handler, restConfig *restclient.Config, builders []APIGroupBuilder, metricsRegistry prometheus.Registerer) (http.Handler, error) {
|
||||
func GetCustomRoutesHandler(delegateHandler http.Handler, restConfig *restclient.Config, builders []APIGroupBuilder, metricsRegistry prometheus.Registerer, apiResourceConfig *serverstorage.ResourceConfig) (http.Handler, error) {
|
||||
useful := false // only true if any routes exist anywhere
|
||||
router := mux.NewRouter()
|
||||
|
||||
@@ -27,6 +29,12 @@ func GetCustomRoutesHandler(delegateHandler http.Handler, restConfig *restclient
|
||||
}
|
||||
|
||||
for _, gv := range GetGroupVersions(builder) {
|
||||
// filter out api groups that are disabled in APIEnablementOptions
|
||||
gvr := gv.WithResource("")
|
||||
if apiResourceConfig != nil && !apiResourceConfig.ResourceEnabled(gvr) {
|
||||
klog.InfoS("Skipping custom route handler for disabled group version", "gv", gv.String())
|
||||
continue
|
||||
}
|
||||
routes := provider.GetAPIRoutes(gv)
|
||||
if routes == nil {
|
||||
continue
|
||||
|
||||
@@ -316,7 +316,11 @@ func (s *service) start(ctx context.Context) error {
|
||||
s.cfg.BuildBranch,
|
||||
)
|
||||
|
||||
if err := o.APIEnablementOptions.ApplyTo(&serverConfig.Config, appinstaller.NewAPIResourceConfig(s.appInstallers), s.scheme); err != nil {
|
||||
apiResourceConfig := appinstaller.NewAPIResourceConfig(s.appInstallers)
|
||||
// add the builder group versions to the api resource config
|
||||
apiResourceConfig.EnableVersions(groupVersions...)
|
||||
|
||||
if err := o.APIEnablementOptions.ApplyTo(&serverConfig.Config, apiResourceConfig, s.scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -359,6 +363,7 @@ func (s *service) start(ctx context.Context) error {
|
||||
groupVersions,
|
||||
defGetters,
|
||||
s.metrics,
|
||||
apiResourceConfig,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -400,6 +405,7 @@ func (s *service) start(ctx context.Context) error {
|
||||
s.features,
|
||||
s.dualWriterMetrics,
|
||||
s.builderMetrics,
|
||||
apiResourceConfig,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user