k8s.io/apiserver@v0.31.1/pkg/server/resourceconfig/helpers.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package resourceconfig
    18  
    19  import (
    20  	"fmt"
    21  	"regexp"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	"k8s.io/apimachinery/pkg/runtime/schema"
    27  	serverstore "k8s.io/apiserver/pkg/server/storage"
    28  	cliflag "k8s.io/component-base/cli/flag"
    29  )
    30  
    31  // GroupVersionRegistry provides access to registered group versions.
    32  type GroupVersionRegistry interface {
    33  	// IsGroupRegistered returns true if given group is registered.
    34  	IsGroupRegistered(group string) bool
    35  	// IsVersionRegistered returns true if given version is registered.
    36  	IsVersionRegistered(v schema.GroupVersion) bool
    37  	// PrioritizedVersionsAllGroups returns all registered group versions.
    38  	PrioritizedVersionsAllGroups() []schema.GroupVersion
    39  }
    40  
    41  // MergeResourceEncodingConfigs merges the given defaultResourceConfig with specific GroupVersionResource overrides.
    42  func MergeResourceEncodingConfigs(
    43  	defaultResourceEncoding *serverstore.DefaultResourceEncodingConfig,
    44  	resourceEncodingOverrides []schema.GroupVersionResource,
    45  ) *serverstore.DefaultResourceEncodingConfig {
    46  	resourceEncodingConfig := defaultResourceEncoding
    47  	for _, gvr := range resourceEncodingOverrides {
    48  		resourceEncodingConfig.SetResourceEncoding(gvr.GroupResource(), gvr.GroupVersion(),
    49  			schema.GroupVersion{Group: gvr.Group, Version: runtime.APIVersionInternal})
    50  	}
    51  	return resourceEncodingConfig
    52  }
    53  
    54  // Recognized values for the --runtime-config parameter to enable/disable groups of APIs
    55  const (
    56  	APIAll   = "api/all"
    57  	APIGA    = "api/ga"
    58  	APIBeta  = "api/beta"
    59  	APIAlpha = "api/alpha"
    60  )
    61  
    62  var (
    63  	gaPattern    = regexp.MustCompile(`^v\d+$`)
    64  	betaPattern  = regexp.MustCompile(`^v\d+beta\d+$`)
    65  	alphaPattern = regexp.MustCompile(`^v\d+alpha\d+$`)
    66  
    67  	groupVersionMatchers = map[string]func(gv schema.GroupVersion) bool{
    68  		// allows users to address all api versions
    69  		APIAll: func(gv schema.GroupVersion) bool { return true },
    70  		// allows users to address all api versions in the form v[0-9]+
    71  		APIGA: func(gv schema.GroupVersion) bool { return gaPattern.MatchString(gv.Version) },
    72  		// allows users to address all beta api versions
    73  		APIBeta: func(gv schema.GroupVersion) bool { return betaPattern.MatchString(gv.Version) },
    74  		// allows users to address all alpha api versions
    75  		APIAlpha: func(gv schema.GroupVersion) bool { return alphaPattern.MatchString(gv.Version) },
    76  	}
    77  
    78  	groupVersionMatchersOrder = []string{APIAll, APIGA, APIBeta, APIAlpha}
    79  )
    80  
    81  // MergeAPIResourceConfigs merges the given defaultAPIResourceConfig with the given resourceConfigOverrides.
    82  // Exclude the groups not registered in registry, and check if version is
    83  // not registered in group, then it will fail.
    84  func MergeAPIResourceConfigs(
    85  	defaultAPIResourceConfig *serverstore.ResourceConfig,
    86  	resourceConfigOverrides cliflag.ConfigurationMap,
    87  	registry GroupVersionRegistry,
    88  ) (*serverstore.ResourceConfig, error) {
    89  	resourceConfig := defaultAPIResourceConfig
    90  	overrides := resourceConfigOverrides
    91  
    92  	for _, flag := range groupVersionMatchersOrder {
    93  		if value, ok := overrides[flag]; ok {
    94  			if value == "false" {
    95  				resourceConfig.DisableMatchingVersions(groupVersionMatchers[flag])
    96  			} else if value == "true" {
    97  				resourceConfig.EnableMatchingVersions(groupVersionMatchers[flag])
    98  			} else {
    99  				return nil, fmt.Errorf("invalid value %v=%v", flag, value)
   100  			}
   101  		}
   102  	}
   103  
   104  	type versionEnablementPreference struct {
   105  		key          string
   106  		enabled      bool
   107  		groupVersion schema.GroupVersion
   108  	}
   109  	type resourceEnablementPreference struct {
   110  		key                  string
   111  		enabled              bool
   112  		groupVersionResource schema.GroupVersionResource
   113  	}
   114  	versionPreferences := []versionEnablementPreference{}
   115  	resourcePreferences := []resourceEnablementPreference{}
   116  
   117  	// "<resourceSpecifier>={true|false} allows users to enable/disable API.
   118  	// This takes preference over api/all, if specified.
   119  	// Iterate through all group/version overrides specified in runtimeConfig.
   120  	for key := range overrides {
   121  		// Have already handled them above. Can skip them here.
   122  		if _, ok := groupVersionMatchers[key]; ok {
   123  			continue
   124  		}
   125  
   126  		tokens := strings.Split(key, "/")
   127  		if len(tokens) < 2 || len(tokens) > 3 {
   128  			continue
   129  		}
   130  		groupVersionString := tokens[0] + "/" + tokens[1]
   131  		groupVersion, err := schema.ParseGroupVersion(groupVersionString)
   132  		if err != nil {
   133  			return nil, fmt.Errorf("invalid key %s", key)
   134  		}
   135  
   136  		// Exclude group not registered into the registry.
   137  		if !registry.IsGroupRegistered(groupVersion.Group) {
   138  			continue
   139  		}
   140  
   141  		// Verify that the groupVersion is registered into registry.
   142  		if !registry.IsVersionRegistered(groupVersion) {
   143  			return nil, fmt.Errorf("group version %s that has not been registered", groupVersion.String())
   144  		}
   145  		enabled, err := getRuntimeConfigValue(overrides, key, false)
   146  		if err != nil {
   147  			return nil, err
   148  		}
   149  
   150  		switch len(tokens) {
   151  		case 2:
   152  			versionPreferences = append(versionPreferences, versionEnablementPreference{
   153  				key:          key,
   154  				enabled:      enabled,
   155  				groupVersion: groupVersion,
   156  			})
   157  		case 3:
   158  			if strings.ToLower(tokens[2]) != tokens[2] {
   159  				return nil, fmt.Errorf("invalid key %v: group/version/resource and resource is always lowercase plural, not %q", key, tokens[2])
   160  			}
   161  			resourcePreferences = append(resourcePreferences, resourceEnablementPreference{
   162  				key:                  key,
   163  				enabled:              enabled,
   164  				groupVersionResource: groupVersion.WithResource(tokens[2]),
   165  			})
   166  		}
   167  	}
   168  
   169  	// apply version preferences first, so that we can remove the hardcoded resource preferences that are being overridden
   170  	for _, versionPreference := range versionPreferences {
   171  		if versionPreference.enabled {
   172  			// enable the groupVersion for "group/version=true"
   173  			resourceConfig.EnableVersions(versionPreference.groupVersion)
   174  
   175  		} else {
   176  			// disable the groupVersion only for "group/version=false"
   177  			resourceConfig.DisableVersions(versionPreference.groupVersion)
   178  		}
   179  	}
   180  
   181  	// apply resource preferences last, so they have the highest priority
   182  	for _, resourcePreference := range resourcePreferences {
   183  		if resourcePreference.enabled {
   184  			// enable the resource for "group/version/resource=true"
   185  			resourceConfig.EnableResources(resourcePreference.groupVersionResource)
   186  		} else {
   187  			resourceConfig.DisableResources(resourcePreference.groupVersionResource)
   188  		}
   189  	}
   190  
   191  	return resourceConfig, nil
   192  }
   193  
   194  func getRuntimeConfigValue(overrides cliflag.ConfigurationMap, apiKey string, defaultValue bool) (bool, error) {
   195  	flagValue, ok := overrides[apiKey]
   196  	if ok {
   197  		if flagValue == "" {
   198  			return true, nil
   199  		}
   200  		boolValue, err := strconv.ParseBool(flagValue)
   201  		if err != nil {
   202  			return false, fmt.Errorf("invalid value of %s: %s, err: %v", apiKey, flagValue, err)
   203  		}
   204  		return boolValue, nil
   205  	}
   206  	return defaultValue, nil
   207  }
   208  
   209  // ParseGroups takes in resourceConfig and returns parsed groups.
   210  func ParseGroups(resourceConfig cliflag.ConfigurationMap) ([]string, error) {
   211  	groups := []string{}
   212  	for key := range resourceConfig {
   213  		if _, ok := groupVersionMatchers[key]; ok {
   214  			continue
   215  		}
   216  		tokens := strings.Split(key, "/")
   217  		if len(tokens) != 2 && len(tokens) != 3 {
   218  			return groups, fmt.Errorf("runtime-config invalid key %s", key)
   219  		}
   220  		groupVersionString := tokens[0] + "/" + tokens[1]
   221  		groupVersion, err := schema.ParseGroupVersion(groupVersionString)
   222  		if err != nil {
   223  			return nil, fmt.Errorf("runtime-config invalid key %s", key)
   224  		}
   225  		groups = append(groups, groupVersion.Group)
   226  	}
   227  
   228  	return groups, nil
   229  }