K8s: read resource configs from API Enablement for API Builders (#114329)

This commit is contained in:
Charandas
2025-11-24 11:09:49 -08:00
committed by GitHub
parent f1dbbcbe00
commit 0c2707bbc4
5 changed files with 111 additions and 36 deletions
-4
View File
@@ -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 {
+78 -28
View File
@@ -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,
+17 -2
View File
@@ -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, &copy, builders)
return addBuilderRoutes(gv, &copy, 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
+7 -1
View File
@@ -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