230 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			230 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
| 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"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"k8s.io/apimachinery/pkg/runtime"
 | |
| 	"k8s.io/apimachinery/pkg/runtime/schema"
 | |
| 	serverstore "k8s.io/apiserver/pkg/server/storage"
 | |
| 	cliflag "k8s.io/component-base/cli/flag"
 | |
| )
 | |
| 
 | |
| // GroupVersionRegistry provides access to registered group versions.
 | |
| type GroupVersionRegistry interface {
 | |
| 	// IsGroupRegistered returns true if given group is registered.
 | |
| 	IsGroupRegistered(group string) bool
 | |
| 	// IsVersionRegistered returns true if given version is registered.
 | |
| 	IsVersionRegistered(v schema.GroupVersion) bool
 | |
| 	// PrioritizedVersionsAllGroups returns all registered group versions.
 | |
| 	PrioritizedVersionsAllGroups() []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
 | |
| }
 | |
| 
 | |
| // Recognized values for the --runtime-config parameter to enable/disable groups of APIs
 | |
| const (
 | |
| 	APIAll   = "api/all"
 | |
| 	APIGA    = "api/ga"
 | |
| 	APIBeta  = "api/beta"
 | |
| 	APIAlpha = "api/alpha"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	gaPattern    = regexp.MustCompile(`^v\d+$`)
 | |
| 	betaPattern  = regexp.MustCompile(`^v\d+beta\d+$`)
 | |
| 	alphaPattern = regexp.MustCompile(`^v\d+alpha\d+$`)
 | |
| 
 | |
| 	groupVersionMatchers = map[string]func(gv schema.GroupVersion) bool{
 | |
| 		// allows users to address all api versions
 | |
| 		APIAll: func(gv schema.GroupVersion) bool { return true },
 | |
| 		// allows users to address all api versions in the form v[0-9]+
 | |
| 		APIGA: func(gv schema.GroupVersion) bool { return gaPattern.MatchString(gv.Version) },
 | |
| 		// allows users to address all beta api versions
 | |
| 		APIBeta: func(gv schema.GroupVersion) bool { return betaPattern.MatchString(gv.Version) },
 | |
| 		// allows users to address all alpha api versions
 | |
| 		APIAlpha: func(gv schema.GroupVersion) bool { return alphaPattern.MatchString(gv.Version) },
 | |
| 	}
 | |
| 
 | |
| 	groupVersionMatchersOrder = []string{APIAll, APIGA, APIBeta, APIAlpha}
 | |
| )
 | |
| 
 | |
| // 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 cliflag.ConfigurationMap,
 | |
| 	registry GroupVersionRegistry,
 | |
| ) (*serverstore.ResourceConfig, error) {
 | |
| 	resourceConfig := defaultAPIResourceConfig
 | |
| 	overrides := resourceConfigOverrides
 | |
| 
 | |
| 	for _, flag := range groupVersionMatchersOrder {
 | |
| 		if value, ok := overrides[flag]; ok {
 | |
| 			if value == "false" {
 | |
| 				resourceConfig.DisableMatchingVersions(groupVersionMatchers[flag])
 | |
| 			} else if value == "true" {
 | |
| 				resourceConfig.EnableMatchingVersions(groupVersionMatchers[flag])
 | |
| 			} else {
 | |
| 				return nil, fmt.Errorf("invalid value %v=%v", flag, value)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	type versionEnablementPreference struct {
 | |
| 		key          string
 | |
| 		enabled      bool
 | |
| 		groupVersion schema.GroupVersion
 | |
| 	}
 | |
| 	type resourceEnablementPreference struct {
 | |
| 		key                  string
 | |
| 		enabled              bool
 | |
| 		groupVersionResource schema.GroupVersionResource
 | |
| 	}
 | |
| 	versionPreferences := []versionEnablementPreference{}
 | |
| 	resourcePreferences := []resourceEnablementPreference{}
 | |
| 
 | |
| 	// "<resourceSpecifier>={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 _, ok := groupVersionMatchers[key]; ok {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		tokens := strings.Split(key, "/")
 | |
| 		if len(tokens) < 2 || len(tokens) > 3 {
 | |
| 			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.IsGroupRegistered(groupVersion.Group) {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Verify that the groupVersion is registered into registry.
 | |
| 		if !registry.IsVersionRegistered(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
 | |
| 		}
 | |
| 
 | |
| 		switch len(tokens) {
 | |
| 		case 2:
 | |
| 			versionPreferences = append(versionPreferences, versionEnablementPreference{
 | |
| 				key:          key,
 | |
| 				enabled:      enabled,
 | |
| 				groupVersion: groupVersion,
 | |
| 			})
 | |
| 		case 3:
 | |
| 			if strings.ToLower(tokens[2]) != tokens[2] {
 | |
| 				return nil, fmt.Errorf("invalid key %v: group/version/resource and resource is always lowercase plural, not %q", key, tokens[2])
 | |
| 			}
 | |
| 			resourcePreferences = append(resourcePreferences, resourceEnablementPreference{
 | |
| 				key:                  key,
 | |
| 				enabled:              enabled,
 | |
| 				groupVersionResource: groupVersion.WithResource(tokens[2]),
 | |
| 			})
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// apply version preferences first, so that we can remove the hardcoded resource preferences that are being overridden
 | |
| 	for _, versionPreference := range versionPreferences {
 | |
| 		if versionPreference.enabled {
 | |
| 			// enable the groupVersion for "group/version=true"
 | |
| 			resourceConfig.EnableVersions(versionPreference.groupVersion)
 | |
| 
 | |
| 		} else {
 | |
| 			// disable the groupVersion only for "group/version=false"
 | |
| 			resourceConfig.DisableVersions(versionPreference.groupVersion)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// apply resource preferences last, so they have the highest priority
 | |
| 	for _, resourcePreference := range resourcePreferences {
 | |
| 		if resourcePreference.enabled {
 | |
| 			// enable the resource for "group/version/resource=true"
 | |
| 			resourceConfig.EnableResources(resourcePreference.groupVersionResource)
 | |
| 		} else {
 | |
| 			resourceConfig.DisableResources(resourcePreference.groupVersionResource)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return resourceConfig, nil
 | |
| }
 | |
| 
 | |
| func getRuntimeConfigValue(overrides cliflag.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 cliflag.ConfigurationMap) ([]string, error) {
 | |
| 	groups := []string{}
 | |
| 	for key := range resourceConfig {
 | |
| 		if _, ok := groupVersionMatchers[key]; ok {
 | |
| 			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
 | |
| }
 |