mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 23:15:14 +00:00
Verify: add static analysis to verify new feature gates are added as versioned feature specs.
Signed-off-by: Siyuan Zhang <sizhang@google.com>
This commit is contained in:
parent
dbc2b0a5c7
commit
35488ef5c7
@ -77,6 +77,7 @@ QUICK_PATTERNS+=(
|
|||||||
"verify-api-groups.sh"
|
"verify-api-groups.sh"
|
||||||
"verify-boilerplate.sh"
|
"verify-boilerplate.sh"
|
||||||
"verify-external-dependencies-version.sh"
|
"verify-external-dependencies-version.sh"
|
||||||
|
"verify-featuregates.sh"
|
||||||
"verify-fieldname-docs.sh"
|
"verify-fieldname-docs.sh"
|
||||||
"verify-gofmt.sh"
|
"verify-gofmt.sh"
|
||||||
"verify-imports.sh"
|
"verify-imports.sh"
|
||||||
|
30
hack/update-featuregates.sh
Executable file
30
hack/update-featuregates.sh
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright 2024 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.
|
||||||
|
|
||||||
|
# This script updates test/featuregates_linter/test_data/unversioned_feature_list.yaml and
|
||||||
|
# test/featuregates_linter/test_data/versioned_feature_list.yaml with all the feature gate features.
|
||||||
|
# Usage: `hack/update-featuregates.sh`.
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
|
||||||
|
source "${KUBE_ROOT}/hack/lib/init.sh"
|
||||||
|
|
||||||
|
cd "${KUBE_ROOT}"
|
||||||
|
|
||||||
|
go run test/featuregates_linter/main.go feature-gates update
|
31
hack/verify-featuregates.sh
Executable file
31
hack/verify-featuregates.sh
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright 2024 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.
|
||||||
|
|
||||||
|
# This script checks test/featuregates_linter/test_data/unversioned_feature_list.yaml and
|
||||||
|
# test/featuregates_linter/test_data/versioned_feature_list.yaml are up to date with all the feature gate features.
|
||||||
|
# We should run `hack/update-featuregates.sh` if the list is out of date.
|
||||||
|
# Usage: `hack/verify-featuregates.sh`.
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
|
||||||
|
source "${KUBE_ROOT}/hack/lib/init.sh"
|
||||||
|
|
||||||
|
cd "${KUBE_ROOT}"
|
||||||
|
|
||||||
|
go run test/featuregates_linter/main.go feature-gates verify
|
14
test/featuregates_linter/OWNERS
Normal file
14
test/featuregates_linter/OWNERS
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# See the OWNERS docs at https://go.k8s.io/owners
|
||||||
|
|
||||||
|
approvers:
|
||||||
|
- feature-approvers
|
||||||
|
- sig-api-machinery-api-approvers
|
||||||
|
- jpbetz
|
||||||
|
- siyuanfoundation
|
||||||
|
reviewers:
|
||||||
|
- feature-approvers
|
||||||
|
- sig-api-machinery-api-reviewers
|
||||||
|
- jpbetz
|
||||||
|
- siyuanfoundation
|
||||||
|
labels:
|
||||||
|
- sig/api-machinery
|
8
test/featuregates_linter/README.md
Normal file
8
test/featuregates_linter/README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
This directory contains static analysis scripts for verify functions.
|
||||||
|
|
||||||
|
Currently, the following commands are implemented:
|
||||||
|
```
|
||||||
|
go run test/featuregates_linter/main.go feature-gates verify-no-new-unversioned --new-features-file="${new_features_file}" --old-features-file="${old_features_file}"
|
||||||
|
|
||||||
|
go run test/featuregates_linter/main.go feature-gates verify-alphabetic-order --features-file="${features_file}"
|
||||||
|
```
|
477
test/featuregates_linter/cmd/feature_gates.go
Normal file
477
test/featuregates_linter/cmd/feature_gates.go
Normal file
@ -0,0 +1,477 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
alphabeticalOrder bool
|
||||||
|
k8RootPath string
|
||||||
|
unversionedFeatureListFile = "test/featuregates_linter/test_data/unversioned_feature_list.yaml"
|
||||||
|
versionedFeatureListFile = "test/featuregates_linter/test_data/versioned_feature_list.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
featureGatePkg = "\"k8s.io/component-base/featuregate\""
|
||||||
|
)
|
||||||
|
|
||||||
|
type featureSpec struct {
|
||||||
|
Default bool `yaml:"default" json:"default"`
|
||||||
|
LockToDefault bool `yaml:"lockToDefault" json:"lockToDefault"`
|
||||||
|
PreRelease string `yaml:"preRelease" json:"preRelease"`
|
||||||
|
Version string `yaml:"version" json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type featureInfo struct {
|
||||||
|
Name string `yaml:"name" json:"name"`
|
||||||
|
FullName string `yaml:"-" json:"-"`
|
||||||
|
VersionedSpecs []featureSpec `yaml:"versionedSpecs" json:"versionedSpecs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFeatureGatesCommand returns the cobra command for "feature-gates".
|
||||||
|
func NewFeatureGatesCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "feature-gates <subcommand>",
|
||||||
|
Short: "Commands related to feature gate verifications and updates",
|
||||||
|
}
|
||||||
|
defaultRootPath, err := filepath.Abs(".")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVar(&k8RootPath, "root-path", defaultRootPath, "absolute path of the k8s repository")
|
||||||
|
|
||||||
|
cmd.AddCommand(NewVerifyFeatureListCommand())
|
||||||
|
cmd.AddCommand(NewUpdateFeatureListCommand())
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVerifyFeatureListCommand() *cobra.Command {
|
||||||
|
cmd := cobra.Command{
|
||||||
|
Use: "verify",
|
||||||
|
Short: "Verifies feature list files are up to date.",
|
||||||
|
Run: verifyFeatureListFunc,
|
||||||
|
}
|
||||||
|
cmd.Flags().BoolVar(&alphabeticalOrder, "alphabetical-order", false, "if true, verify all features in any FeatureSpec map are ordered aphabetically")
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpdateFeatureListCommand() *cobra.Command {
|
||||||
|
cmd := cobra.Command{
|
||||||
|
Use: "update",
|
||||||
|
Short: "updates feature list files.",
|
||||||
|
Run: updateFeatureListFunc,
|
||||||
|
}
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyFeatureListFunc(cmd *cobra.Command, args []string) {
|
||||||
|
if err := verifyOrUpdateFeatureList(k8RootPath, unversionedFeatureListFile, false, false); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := verifyOrUpdateFeatureList(k8RootPath, versionedFeatureListFile, false, true); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateFeatureListFunc(cmd *cobra.Command, args []string) {
|
||||||
|
if err := verifyOrUpdateFeatureList(k8RootPath, unversionedFeatureListFile, true, false); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := verifyOrUpdateFeatureList(k8RootPath, versionedFeatureListFile, true, true); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyOrUpdateFeatureList walks all the files under pkg/ and staging/ to find the list of all the features in
|
||||||
|
// map[featuregate.Feature]featuregate.FeatureSpec or map[featuregate.Feature]featuregate.VersionedSpecs.
|
||||||
|
// It will then update the feature list in featureListFile, or verifies there is no change from the existing list.
|
||||||
|
func verifyOrUpdateFeatureList(rootPath, featureListFile string, update, versioned bool) error {
|
||||||
|
featureList := []featureInfo{}
|
||||||
|
features, err := searchPathForFeatures(filepath.Join(rootPath, "pkg"), versioned)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
featureList = append(featureList, features...)
|
||||||
|
|
||||||
|
features, err = searchPathForFeatures(filepath.Join(rootPath, "staging"), versioned)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
featureList = append(featureList, features...)
|
||||||
|
|
||||||
|
sort.Slice(featureList, func(i, j int) bool {
|
||||||
|
return strings.ToLower(featureList[i].Name) < strings.ToLower(featureList[j].Name)
|
||||||
|
})
|
||||||
|
featureList, err = dedupeFeatureList(featureList)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Join(rootPath, featureListFile)
|
||||||
|
baseFeatureListBytes, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
baseFeatureList := []featureInfo{}
|
||||||
|
err = yaml.Unmarshal(baseFeatureListBytes, &baseFeatureList)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// only feature deletion is allowed for unversioned features.
|
||||||
|
// all new features or feature updates should be migrated to versioned feature gate.
|
||||||
|
// https://github.com/kubernetes/kubernetes/issues/125031
|
||||||
|
if !versioned {
|
||||||
|
if err := verifyFeatureDeletionOnly(featureList, baseFeatureList); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if update {
|
||||||
|
data, err := yaml.Marshal(featureList)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(filePath, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(featureList, baseFeatureList); diff != "" {
|
||||||
|
return fmt.Errorf("detected diff in unversioned feature list, diff: \n%s", diff)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dedupeFeatureList(featureList []featureInfo) ([]featureInfo, error) {
|
||||||
|
if featureList == nil || len(featureList) < 1 {
|
||||||
|
return featureList, nil
|
||||||
|
}
|
||||||
|
last := featureList[0]
|
||||||
|
// clean up FullName field for the final output
|
||||||
|
last.FullName = ""
|
||||||
|
deduped := []featureInfo{last}
|
||||||
|
for i := 1; i < len(featureList); i++ {
|
||||||
|
f := featureList[i]
|
||||||
|
if f.Name == last.Name {
|
||||||
|
// if it is a duplicate feature, verify the lifecycles are the same
|
||||||
|
if !reflect.DeepEqual(last.VersionedSpecs, f.VersionedSpecs) {
|
||||||
|
return deduped, fmt.Errorf("multiple conflicting specs found for feature:%s, [\n%v, \n%v]", last.Name, last.VersionedSpecs, f.VersionedSpecs)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
last = f
|
||||||
|
last.FullName = ""
|
||||||
|
deduped = append(deduped, last)
|
||||||
|
|
||||||
|
}
|
||||||
|
return deduped, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyFeatureDeletionOnly(newFeatureList []featureInfo, oldFeatureList []featureInfo) error {
|
||||||
|
oldFeatureSet := make(map[string]*featureInfo)
|
||||||
|
for _, f := range oldFeatureList {
|
||||||
|
oldFeatureSet[f.Name] = &f
|
||||||
|
}
|
||||||
|
newFeatures := []string{}
|
||||||
|
for _, f := range newFeatureList {
|
||||||
|
oldSpecs, found := oldFeatureSet[f.Name]
|
||||||
|
if !found {
|
||||||
|
newFeatures = append(newFeatures, f.Name)
|
||||||
|
} else if !reflect.DeepEqual(*oldSpecs, f) {
|
||||||
|
return fmt.Errorf("feature %s changed with diff: %s", f.Name, cmp.Diff(*oldSpecs, f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(newFeatures) > 0 {
|
||||||
|
return fmt.Errorf("new features added to FeatureSpec map! %v\nPlease add new features through VersionedSpecs map ONLY! ", newFeatures)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchPathForFeatures(path string, versioned bool) ([]featureInfo, error) {
|
||||||
|
allFeatures := []featureInfo{}
|
||||||
|
// Create a FileSet to work with
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if strings.HasPrefix(path, "vendor") || strings.HasPrefix(path, "_") {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(path, ".go") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(path, "_test.go") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
features, parseErr := extractFeatureInfoListFromFile(fset, path, versioned)
|
||||||
|
if parseErr != nil {
|
||||||
|
return parseErr
|
||||||
|
}
|
||||||
|
allFeatures = append(allFeatures, features...)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return allFeatures, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractFeatureInfoListFromFile extracts info all the the features from
|
||||||
|
// map[featuregate.Feature]featuregate.FeatureSpec or map[featuregate.Feature]featuregate.VersionedSpecs from the given file.
|
||||||
|
func extractFeatureInfoListFromFile(fset *token.FileSet, filePath string, versioned bool) (allFeatures []featureInfo, err error) {
|
||||||
|
// Parse the file and create an AST
|
||||||
|
absFilePath, err := filepath.Abs(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return allFeatures, err
|
||||||
|
}
|
||||||
|
file, err := parser.ParseFile(fset, absFilePath, nil, parser.AllErrors)
|
||||||
|
if err != nil {
|
||||||
|
return allFeatures, err
|
||||||
|
}
|
||||||
|
aliasMap := importAliasMap(file.Imports)
|
||||||
|
// any file containing features should have imported the featuregate pkg.
|
||||||
|
if _, ok := aliasMap[featureGatePkg]; !ok {
|
||||||
|
return allFeatures, err
|
||||||
|
}
|
||||||
|
variables := globalVariableDeclarations(file)
|
||||||
|
|
||||||
|
for _, d := range file.Decls {
|
||||||
|
if gd, ok := d.(*ast.GenDecl); ok && (gd.Tok == token.CONST || gd.Tok == token.VAR) {
|
||||||
|
for _, spec := range gd.Specs {
|
||||||
|
if vspec, ok := spec.(*ast.ValueSpec); ok {
|
||||||
|
for _, name := range vspec.Names {
|
||||||
|
for _, value := range vspec.Values {
|
||||||
|
features, err := extractFeatureInfoList(filePath, value, aliasMap, variables, versioned)
|
||||||
|
if err != nil {
|
||||||
|
return allFeatures, err
|
||||||
|
}
|
||||||
|
if len(features) > 0 {
|
||||||
|
fmt.Printf("found %d features in FeatureSpecMap var %s in file: %s\n", len(features), name, filePath)
|
||||||
|
allFeatures = append(allFeatures, features...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fd, ok := d.(*ast.FuncDecl); ok {
|
||||||
|
for _, stmt := range fd.Body.List {
|
||||||
|
if st, ok := stmt.(*ast.ReturnStmt); ok {
|
||||||
|
for _, value := range st.Results {
|
||||||
|
features, err := extractFeatureInfoList(filePath, value, aliasMap, variables, versioned)
|
||||||
|
if err != nil {
|
||||||
|
return allFeatures, err
|
||||||
|
}
|
||||||
|
if len(features) > 0 {
|
||||||
|
fmt.Printf("found %d features in FeatureSpecMap of func %s in file: %s\n", len(features), fd.Name, filePath)
|
||||||
|
allFeatures = append(allFeatures, features...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPkgPrefix(s string) string {
|
||||||
|
if strings.Contains(s, ".") {
|
||||||
|
return strings.Split(s, ".")[0]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyAlphabeticOrder(keys []string, path string) error {
|
||||||
|
keysSorted := make([]string, len(keys))
|
||||||
|
copy(keysSorted, keys)
|
||||||
|
sort.Slice(keysSorted, func(i, j int) bool {
|
||||||
|
keyI := strings.ToLower(keysSorted[i])
|
||||||
|
keyJ := strings.ToLower(keysSorted[j])
|
||||||
|
if getPkgPrefix(keyI) == getPkgPrefix(keyJ) {
|
||||||
|
return keyI < keyJ
|
||||||
|
}
|
||||||
|
return getPkgPrefix(keyI) < getPkgPrefix(keyJ)
|
||||||
|
})
|
||||||
|
if diff := cmp.Diff(keys, keysSorted); diff != "" {
|
||||||
|
return fmt.Errorf("features in %s are not in alphabetic order, diff: %s", path, diff)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractFeatureInfoList extracts the info all the the features from
|
||||||
|
// map[featuregate.Feature]featuregate.FeatureSpec or map[featuregate.Feature]featuregate.VersionedSpecs.
|
||||||
|
func extractFeatureInfoList(filePath string, v ast.Expr, aliasMap map[string]string, variables map[string]ast.Expr, versioned bool) ([]featureInfo, error) {
|
||||||
|
keys := []string{}
|
||||||
|
features := []featureInfo{}
|
||||||
|
cl, ok := v.(*ast.CompositeLit)
|
||||||
|
if !ok {
|
||||||
|
return features, nil
|
||||||
|
}
|
||||||
|
mt, ok := cl.Type.(*ast.MapType)
|
||||||
|
if !ok {
|
||||||
|
return features, nil
|
||||||
|
}
|
||||||
|
if !isFeatureSpecType(mt.Value, aliasMap, versioned) {
|
||||||
|
return features, nil
|
||||||
|
}
|
||||||
|
for _, elt := range cl.Elts {
|
||||||
|
kv, ok := elt.(*ast.KeyValueExpr)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
info, err := parseFeatureInfo(variables, kv, versioned)
|
||||||
|
if err != nil {
|
||||||
|
return features, err
|
||||||
|
}
|
||||||
|
features = append(features, info)
|
||||||
|
keys = append(keys, info.FullName)
|
||||||
|
}
|
||||||
|
if alphabeticalOrder {
|
||||||
|
// verifies the features are sorted in the map
|
||||||
|
if err := verifyAlphabeticOrder(keys, filePath); err != nil {
|
||||||
|
return features, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return features, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFeatureSpecType(v ast.Expr, aliasMap map[string]string, versioned bool) bool {
|
||||||
|
typeName := "FeatureSpec"
|
||||||
|
if versioned {
|
||||||
|
typeName = "VersionedSpecs"
|
||||||
|
}
|
||||||
|
alias, ok := aliasMap[featureGatePkg]
|
||||||
|
if ok {
|
||||||
|
typeName = alias + "." + typeName
|
||||||
|
}
|
||||||
|
return identifierName(v, false) == typeName
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFeatureInfo(variables map[string]ast.Expr, kv *ast.KeyValueExpr, versioned bool) (featureInfo, error) {
|
||||||
|
info := featureInfo{
|
||||||
|
Name: identifierName(kv.Key, true),
|
||||||
|
FullName: identifierName(kv.Key, false),
|
||||||
|
VersionedSpecs: []featureSpec{},
|
||||||
|
}
|
||||||
|
specExps := []ast.Expr{}
|
||||||
|
if versioned {
|
||||||
|
if cl, ok := kv.Value.(*ast.CompositeLit); ok {
|
||||||
|
specExps = append(specExps, cl.Elts...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
specExps = append(specExps, kv.Value)
|
||||||
|
}
|
||||||
|
for _, specExp := range specExps {
|
||||||
|
spec, err := parseFeatureSpec(variables, specExp)
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
info.VersionedSpecs = append(info.VersionedSpecs, spec)
|
||||||
|
}
|
||||||
|
// verify FeatureSpec in VersionedSpecs are ordered by version.
|
||||||
|
if len(info.VersionedSpecs) > 1 {
|
||||||
|
specsSorted := make([]featureSpec, len(info.VersionedSpecs))
|
||||||
|
copy(specsSorted, info.VersionedSpecs)
|
||||||
|
sort.Slice(specsSorted, func(i, j int) bool {
|
||||||
|
verI := version.MustParse(specsSorted[i].Version)
|
||||||
|
verJ := version.MustParse(specsSorted[j].Version)
|
||||||
|
return verI.LessThan(verJ)
|
||||||
|
})
|
||||||
|
if diff := cmp.Diff(info.VersionedSpecs, specsSorted); diff != "" {
|
||||||
|
return info, fmt.Errorf("VersionedSpecs in feature %s are not ordered by version, diff: %s", info.Name, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFeatureSpec(variables map[string]ast.Expr, v ast.Expr) (featureSpec, error) {
|
||||||
|
spec := featureSpec{}
|
||||||
|
cl, ok := v.(*ast.CompositeLit)
|
||||||
|
if !ok {
|
||||||
|
return spec, fmt.Errorf("expect FeatureSpec to be a CompositeLit")
|
||||||
|
}
|
||||||
|
for _, elt := range cl.Elts {
|
||||||
|
switch eltType := elt.(type) {
|
||||||
|
case *ast.KeyValueExpr:
|
||||||
|
key := identifierName(eltType.Key, true)
|
||||||
|
switch key {
|
||||||
|
case "Default":
|
||||||
|
boolValue, err := parseBool(variables, eltType.Value)
|
||||||
|
if err != nil {
|
||||||
|
return spec, err
|
||||||
|
}
|
||||||
|
spec.Default = boolValue
|
||||||
|
|
||||||
|
case "LockToDefault":
|
||||||
|
boolValue, err := parseBool(variables, eltType.Value)
|
||||||
|
if err != nil {
|
||||||
|
return spec, err
|
||||||
|
}
|
||||||
|
spec.LockToDefault = boolValue
|
||||||
|
|
||||||
|
case "PreRelease":
|
||||||
|
spec.PreRelease = identifierName(eltType.Value, true)
|
||||||
|
|
||||||
|
case "Version":
|
||||||
|
ver, err := parseVersion(eltType.Value)
|
||||||
|
if err != nil {
|
||||||
|
return spec, err
|
||||||
|
}
|
||||||
|
spec.Version = ver
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return spec, fmt.Errorf("cannot parse FeatureSpec")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return spec, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseVersion(v ast.Expr) (string, error) {
|
||||||
|
fc, ok := v.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("expect FeatureSpec Version to be a function call")
|
||||||
|
}
|
||||||
|
funcName := identifierName(fc.Fun, true)
|
||||||
|
switch funcName {
|
||||||
|
case "MustParse":
|
||||||
|
return basicStringLiteral(fc.Args[0])
|
||||||
|
|
||||||
|
case "MajorMinor":
|
||||||
|
major, err := basicIntLiteral(fc.Args[0])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
minor, err := basicIntLiteral(fc.Args[1])
|
||||||
|
return fmt.Sprintf("%d.%d", major, minor), err
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unrecognized function call in FeatureSpec Version")
|
||||||
|
}
|
||||||
|
}
|
985
test/featuregates_linter/cmd/feature_gates_test.go
Normal file
985
test/featuregates_linter/cmd/feature_gates_test.go
Normal file
@ -0,0 +1,985 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVerifyAlphabeticOrder(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
keys []string
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ordered versioned specs",
|
||||||
|
keys: []string{
|
||||||
|
"SchedulerQueueingHints", "SELinuxMount", "ServiceAccountTokenJTI",
|
||||||
|
"genericfeatures.AdmissionWebhookMatchConditions",
|
||||||
|
"genericfeatures.AggregatedDiscoveryEndpoint",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unordered versioned specs",
|
||||||
|
keys: []string{
|
||||||
|
"SELinuxMount", "SchedulerQueueingHints", "ServiceAccountTokenJTI",
|
||||||
|
"genericfeatures.AdmissionWebhookMatchConditions",
|
||||||
|
"genericfeatures.AggregatedDiscoveryEndpoint",
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unordered versioned specs with mixed pkg prefix",
|
||||||
|
keys: []string{
|
||||||
|
"genericfeatures.AdmissionWebhookMatchConditions",
|
||||||
|
"SchedulerQueueingHints", "SELinuxMount", "ServiceAccountTokenJTI",
|
||||||
|
"genericfeatures.AggregatedDiscoveryEndpoint",
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unordered versioned specs with pkg prefix",
|
||||||
|
keys: []string{
|
||||||
|
"SchedulerQueueingHints", "SELinuxMount", "ServiceAccountTokenJTI",
|
||||||
|
"genericfeatures.AggregatedDiscoveryEndpoint",
|
||||||
|
"genericfeatures.AdmissionWebhookMatchConditions",
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := verifyAlphabeticOrder(tc.keys, "")
|
||||||
|
if tc.expectErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error, got nil")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyOrUpdateFeatureListUnversioned(t *testing.T) {
|
||||||
|
featureListFileContent := `- name: AppArmorFields
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ClusterTrustBundleProjection
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: CPUCFSQuotaPeriod
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
`
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
goFileContent string
|
||||||
|
updatedFeatureListFileContent string
|
||||||
|
expectVerifyErr bool
|
||||||
|
expectUpdateErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no change",
|
||||||
|
goFileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||||
|
AppArmorFields: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
CPUCFSQuotaPeriod: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
ClusterTrustBundleProjection: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
}
|
||||||
|
var otherFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||||
|
AppArmorFields: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
updatedFeatureListFileContent: featureListFileContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same feature added twice with different lifecycle",
|
||||||
|
goFileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||||
|
AppArmorFields: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
CPUCFSQuotaPeriod: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
ClusterTrustBundleProjection: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
}
|
||||||
|
var otherFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||||
|
AppArmorFields: {Default: true, PreRelease: featuregate.Alpha},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectVerifyErr: true,
|
||||||
|
expectUpdateErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "new feature added",
|
||||||
|
goFileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||||
|
AppArmorFields: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
CPUCFSQuotaPeriod: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
ClusterTrustBundleProjection: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
SELinuxMount: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectVerifyErr: true,
|
||||||
|
expectUpdateErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete feature",
|
||||||
|
goFileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||||
|
AppArmorFields: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
ClusterTrustBundleProjection: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectVerifyErr: true,
|
||||||
|
updatedFeatureListFileContent: `- name: AppArmorFields
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ClusterTrustBundleProjection
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "update feature",
|
||||||
|
goFileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||||
|
AppArmorFields: {Default: true, PreRelease: featuregate.GA},
|
||||||
|
CPUCFSQuotaPeriod: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
ClusterTrustBundleProjection: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectVerifyErr: true,
|
||||||
|
expectUpdateErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
featureListFile := writeContentToTmpFile(t, "", "feature_list.yaml", strings.TrimSpace(featureListFileContent))
|
||||||
|
tmpDir := filepath.Dir(featureListFile.Name())
|
||||||
|
_ = writeContentToTmpFile(t, tmpDir, "pkg/new_features.go", tc.goFileContent)
|
||||||
|
err := verifyOrUpdateFeatureList(tmpDir, filepath.Base(featureListFile.Name()), false, false)
|
||||||
|
if tc.expectVerifyErr != (err != nil) {
|
||||||
|
t.Errorf("expectVerifyErr=%v, got err: %s", tc.expectVerifyErr, err)
|
||||||
|
}
|
||||||
|
err = verifyOrUpdateFeatureList(tmpDir, filepath.Base(featureListFile.Name()), true, false)
|
||||||
|
if tc.expectUpdateErr != (err != nil) {
|
||||||
|
t.Errorf("expectVerifyErr=%v, got err: %s", tc.expectVerifyErr, err)
|
||||||
|
}
|
||||||
|
if tc.expectUpdateErr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updatedFeatureListFileContent, err := os.ReadFile(featureListFile.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(string(updatedFeatureListFileContent), tc.updatedFeatureListFileContent); diff != "" {
|
||||||
|
t.Errorf("updatedFeatureListFileContent does not match expected, diff=%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyOrUpdateFeatureListVersioned(t *testing.T) {
|
||||||
|
featureListFileContent := `- name: APIListChunking
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: "1.30"
|
||||||
|
- name: AppArmorFields
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: "1.30"
|
||||||
|
- name: CPUCFSQuotaPeriod
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: "1.30"
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: "1.31"
|
||||||
|
`
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
goFileContent string
|
||||||
|
updatedFeatureListFileContent string
|
||||||
|
expectVerifyErr bool
|
||||||
|
expectUpdateErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no change",
|
||||||
|
goFileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{
|
||||||
|
AppArmorFields: {
|
||||||
|
{Version: version.MajorMinor(1, 30), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
CPUCFSQuotaPeriod: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
{Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
genericfeatures.APIListChunking: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var otherFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{
|
||||||
|
AppArmorFields: {
|
||||||
|
{Version: version.MajorMinor(1, 30), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
updatedFeatureListFileContent: featureListFileContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same feature added twice with different lifecycle",
|
||||||
|
goFileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{
|
||||||
|
AppArmorFields: {
|
||||||
|
{Version: version.MajorMinor(1, 30), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
CPUCFSQuotaPeriod: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
{Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
genericfeatures.APIListChunking: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var otherFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{
|
||||||
|
AppArmorFields: {
|
||||||
|
{Version: version.MajorMinor(1, 30), Default: true, PreRelease: featuregate.Alpha},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectVerifyErr: true,
|
||||||
|
expectUpdateErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "VersionedSpecs not ordered by version",
|
||||||
|
goFileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{
|
||||||
|
AppArmorFields: {
|
||||||
|
{Version: version.MajorMinor(1, 30), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
CPUCFSQuotaPeriod: {
|
||||||
|
{Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
genericfeatures.APIListChunking: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectVerifyErr: true,
|
||||||
|
expectUpdateErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add new feature",
|
||||||
|
goFileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{
|
||||||
|
AppArmorFields: {
|
||||||
|
{Version: version.MajorMinor(1, 30), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
ClusterTrustBundleProjection: {
|
||||||
|
{Version: version.MajorMinor(1, 30), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
CPUCFSQuotaPeriod: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
{Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
genericfeatures.APIListChunking: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectVerifyErr: true,
|
||||||
|
updatedFeatureListFileContent: `- name: APIListChunking
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: "1.30"
|
||||||
|
- name: AppArmorFields
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: "1.30"
|
||||||
|
- name: ClusterTrustBundleProjection
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: "1.30"
|
||||||
|
- name: CPUCFSQuotaPeriod
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: "1.30"
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: "1.31"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remove feature",
|
||||||
|
goFileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{
|
||||||
|
CPUCFSQuotaPeriod: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
{Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
genericfeatures.APIListChunking: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectVerifyErr: true,
|
||||||
|
updatedFeatureListFileContent: `- name: APIListChunking
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: "1.30"
|
||||||
|
- name: CPUCFSQuotaPeriod
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: "1.30"
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: "1.31"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "update feature",
|
||||||
|
goFileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{
|
||||||
|
AppArmorFields: {
|
||||||
|
{Version: version.MajorMinor(1, 30), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
CPUCFSQuotaPeriod: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
{Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
{Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA},
|
||||||
|
},
|
||||||
|
genericfeatures.APIListChunking: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectVerifyErr: true,
|
||||||
|
updatedFeatureListFileContent: `- name: APIListChunking
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: "1.30"
|
||||||
|
- name: AppArmorFields
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: "1.30"
|
||||||
|
- name: CPUCFSQuotaPeriod
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: "1.30"
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: "1.31"
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: GA
|
||||||
|
version: "1.32"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
featureListFile := writeContentToTmpFile(t, "", "feature_list.yaml", strings.TrimSpace(featureListFileContent))
|
||||||
|
tmpDir := filepath.Dir(featureListFile.Name())
|
||||||
|
_ = writeContentToTmpFile(t, tmpDir, "pkg/new_features.go", tc.goFileContent)
|
||||||
|
err := verifyOrUpdateFeatureList(tmpDir, filepath.Base(featureListFile.Name()), false, true)
|
||||||
|
if tc.expectVerifyErr != (err != nil) {
|
||||||
|
t.Errorf("expectVerifyErr=%v, got err: %s", tc.expectVerifyErr, err)
|
||||||
|
}
|
||||||
|
err = verifyOrUpdateFeatureList(tmpDir, filepath.Base(featureListFile.Name()), true, true)
|
||||||
|
if tc.expectUpdateErr != (err != nil) {
|
||||||
|
t.Errorf("expectVerifyErr=%v, got err: %s", tc.expectVerifyErr, err)
|
||||||
|
}
|
||||||
|
if tc.expectUpdateErr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updatedFeatureListFileContent, err := os.ReadFile(featureListFile.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(string(updatedFeatureListFileContent), tc.updatedFeatureListFileContent); diff != "" {
|
||||||
|
t.Errorf("updatedFeatureListFileContent does not match expected, diff=%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractFeatureInfoListFromFile(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fileContent string
|
||||||
|
expectedFeatures []featureInfo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "map in var",
|
||||||
|
fileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||||
|
AppArmorFields: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
CPUCFSQuotaPeriod: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
genericfeatures.AggregatedDiscoveryEndpoint: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedFeatures: []featureInfo{
|
||||||
|
{
|
||||||
|
Name: "AppArmorFields",
|
||||||
|
FullName: "AppArmorFields",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: true, PreRelease: "Beta"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "CPUCFSQuotaPeriod",
|
||||||
|
FullName: "CPUCFSQuotaPeriod",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: false, PreRelease: "Alpha"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "AggregatedDiscoveryEndpoint",
|
||||||
|
FullName: "genericfeatures.AggregatedDiscoveryEndpoint",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: false, PreRelease: "Alpha"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "map in var with alias",
|
||||||
|
fileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||||
|
fg "k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
const (
|
||||||
|
CPUCFSQuotaPeriodDefault = false
|
||||||
|
)
|
||||||
|
var defaultVersionedKubernetesFeatureGates = map[fg.Feature]fg.FeatureSpec{
|
||||||
|
AppArmorFields: {Default: true, PreRelease: fg.Beta},
|
||||||
|
CPUCFSQuotaPeriod: {Default: CPUCFSQuotaPeriodDefault, PreRelease: fg.Alpha},
|
||||||
|
genericfeatures.AggregatedDiscoveryEndpoint: {Default: false, PreRelease: fg.Alpha},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedFeatures: []featureInfo{
|
||||||
|
{
|
||||||
|
Name: "AppArmorFields",
|
||||||
|
FullName: "AppArmorFields",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: true, PreRelease: "Beta"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "CPUCFSQuotaPeriod",
|
||||||
|
FullName: "CPUCFSQuotaPeriod",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: false, PreRelease: "Alpha"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "AggregatedDiscoveryEndpoint",
|
||||||
|
FullName: "genericfeatures.AggregatedDiscoveryEndpoint",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: false, PreRelease: "Alpha"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "map in function return statement",
|
||||||
|
fileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ComponentSLIs featuregate.Feature = "ComponentSLIs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func featureGates() map[featuregate.Feature]featuregate.FeatureSpec {
|
||||||
|
return map[featuregate.Feature]featuregate.FeatureSpec{
|
||||||
|
ComponentSLIs: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedFeatures: []featureInfo{
|
||||||
|
{
|
||||||
|
Name: "ComponentSLIs",
|
||||||
|
FullName: "ComponentSLIs",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: true, PreRelease: "Beta"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// name: "map in function call",
|
||||||
|
// fileContent: `
|
||||||
|
// package features
|
||||||
|
|
||||||
|
// import (
|
||||||
|
// "k8s.io/component-base/featuregate"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// const (
|
||||||
|
// ComponentSLIs featuregate.Feature = "ComponentSLIs"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// func featureGates() featuregate.FeatureGate {
|
||||||
|
// featureGate := featuregate.NewFeatureGate()
|
||||||
|
// _ = featureGate.Add(map[featuregate.Feature]featuregate.FeatureSpec{
|
||||||
|
// ComponentSLIs: {
|
||||||
|
// Default: true, PreRelease: featuregate.Beta}})
|
||||||
|
// return featureGate
|
||||||
|
// }
|
||||||
|
// `,
|
||||||
|
// },
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
newFile := writeContentToTmpFile(t, "", "new_features.go", tc.fileContent)
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
features, err := extractFeatureInfoListFromFile(fset, newFile.Name(), false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(features, tc.expectedFeatures); diff != "" {
|
||||||
|
t.Errorf("File contents: got=%v, want=%v, diff=%s", features, tc.expectedFeatures, diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractFeatureInfoListFromFileVersioned(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fileContent string
|
||||||
|
expectedFeatures []featureInfo
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "map in var",
|
||||||
|
fileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{
|
||||||
|
AppArmorFields: {
|
||||||
|
{Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
CPUCFSQuotaPeriod: {
|
||||||
|
{Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
},
|
||||||
|
genericfeatures.AggregatedDiscoveryEndpoint: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedFeatures: []featureInfo{
|
||||||
|
{
|
||||||
|
Name: "AppArmorFields",
|
||||||
|
FullName: "AppArmorFields",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: true, PreRelease: "Beta", Version: "1.31"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "CPUCFSQuotaPeriod",
|
||||||
|
FullName: "CPUCFSQuotaPeriod",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: false, PreRelease: "Alpha", Version: "1.29"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "AggregatedDiscoveryEndpoint",
|
||||||
|
FullName: "genericfeatures.AggregatedDiscoveryEndpoint",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: false, PreRelease: "Alpha", Version: "1.30"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "map in var with alias",
|
||||||
|
fileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
|
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||||
|
fg "k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
const (
|
||||||
|
CPUCFSQuotaPeriodDefault = false
|
||||||
|
)
|
||||||
|
var defaultVersionedKubernetesFeatureGates = map[fg.Feature]fg.VersionedSpecs{
|
||||||
|
AppArmorFields: {
|
||||||
|
{Version: version.MustParse("1.31"), Default: true, PreRelease: fg.Beta},
|
||||||
|
},
|
||||||
|
CPUCFSQuotaPeriod: {
|
||||||
|
{Version: version.MustParse("1.29"), Default: false, PreRelease: fg.Alpha},
|
||||||
|
},
|
||||||
|
genericfeatures.AggregatedDiscoveryEndpoint: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: false, PreRelease: fg.Alpha},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedFeatures: []featureInfo{
|
||||||
|
{
|
||||||
|
Name: "AppArmorFields",
|
||||||
|
FullName: "AppArmorFields",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: true, PreRelease: "Beta", Version: "1.31"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "CPUCFSQuotaPeriod",
|
||||||
|
FullName: "CPUCFSQuotaPeriod",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: false, PreRelease: "Alpha", Version: "1.29"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "AggregatedDiscoveryEndpoint",
|
||||||
|
FullName: "genericfeatures.AggregatedDiscoveryEndpoint",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: false, PreRelease: "Alpha", Version: "1.30"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "map in function return statement",
|
||||||
|
fileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ComponentSLIs featuregate.Feature = "ComponentSLIs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func featureGates() map[featuregate.Feature]featuregate.VersionedSpecs {
|
||||||
|
return map[featuregate.Feature]featuregate.VersionedSpecs{
|
||||||
|
ComponentSLIs: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
{Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
{Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedFeatures: []featureInfo{
|
||||||
|
{
|
||||||
|
Name: "ComponentSLIs",
|
||||||
|
FullName: "ComponentSLIs",
|
||||||
|
VersionedSpecs: []featureSpec{
|
||||||
|
{Default: false, PreRelease: "Alpha", Version: "1.30"},
|
||||||
|
{Default: true, PreRelease: "Beta", Version: "1.31"},
|
||||||
|
{Default: true, PreRelease: "GA", Version: "1.32", LockToDefault: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error when VersionedSpecs not ordered by version",
|
||||||
|
fileContent: `
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ComponentSLIs featuregate.Feature = "ComponentSLIs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func featureGates() map[featuregate.Feature]featuregate.VersionedSpecs {
|
||||||
|
return map[featuregate.Feature]featuregate.VersionedSpecs{
|
||||||
|
ComponentSLIs: {
|
||||||
|
{Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
{Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
{Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
newFile := writeContentToTmpFile(t, "", "new_features.go", tc.fileContent)
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
features, err := extractFeatureInfoListFromFile(fset, newFile.Name(), true)
|
||||||
|
if tc.expectErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expect err")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(features, tc.expectedFeatures); diff != "" {
|
||||||
|
t.Errorf("File contents: got=%v, want=%v, diff=%s", features, tc.expectedFeatures, diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeContentToTmpFile(t *testing.T, tmpDir, fileName, fileContent string) *os.File {
|
||||||
|
if tmpDir == "" {
|
||||||
|
p, err := os.MkdirTemp("", "k8s")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tmpDir = p
|
||||||
|
}
|
||||||
|
fullPath := filepath.Join(tmpDir, fileName)
|
||||||
|
err := os.MkdirAll(filepath.Dir(fullPath), os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tmpfile, err := os.Create(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = tmpfile.WriteString(fileContent)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = tmpfile.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("sizhangDebug: Written tmp file %s\n", tmpfile.Name())
|
||||||
|
return tmpfile
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFeatureSpec(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
val ast.Expr
|
||||||
|
expectedFeatureSpec featureSpec
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "spec by field name",
|
||||||
|
expectedFeatureSpec: featureSpec{
|
||||||
|
Default: true, LockToDefault: true, PreRelease: "Beta", Version: "1.31",
|
||||||
|
},
|
||||||
|
val: &ast.CompositeLit{
|
||||||
|
Elts: []ast.Expr{
|
||||||
|
&ast.KeyValueExpr{
|
||||||
|
Key: &ast.Ident{
|
||||||
|
Name: "Version",
|
||||||
|
},
|
||||||
|
Value: &ast.CallExpr{
|
||||||
|
Fun: &ast.SelectorExpr{
|
||||||
|
X: &ast.Ident{
|
||||||
|
Name: "version",
|
||||||
|
},
|
||||||
|
Sel: &ast.Ident{
|
||||||
|
Name: "MustParse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Args: []ast.Expr{
|
||||||
|
&ast.BasicLit{
|
||||||
|
Kind: token.STRING,
|
||||||
|
Value: "\"1.31\"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ast.KeyValueExpr{
|
||||||
|
Key: &ast.Ident{
|
||||||
|
Name: "Default",
|
||||||
|
},
|
||||||
|
Value: &ast.Ident{
|
||||||
|
Name: "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ast.KeyValueExpr{
|
||||||
|
Key: &ast.Ident{
|
||||||
|
Name: "LockToDefault",
|
||||||
|
},
|
||||||
|
Value: &ast.Ident{
|
||||||
|
Name: "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ast.KeyValueExpr{
|
||||||
|
Key: &ast.Ident{
|
||||||
|
Name: "PreRelease",
|
||||||
|
},
|
||||||
|
Value: &ast.SelectorExpr{
|
||||||
|
X: &ast.Ident{
|
||||||
|
Name: "featuregate",
|
||||||
|
},
|
||||||
|
Sel: &ast.Ident{
|
||||||
|
Name: "Beta",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
variables := map[string]ast.Expr{}
|
||||||
|
spec, err := parseFeatureSpec(variables, tc.val)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tc.expectedFeatureSpec, spec) {
|
||||||
|
t.Errorf("expected: %#v, got %#v", tc.expectedFeatureSpec, spec)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
40
test/featuregates_linter/cmd/root.go
Normal file
40
test/featuregates_linter/cmd/root.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "static-analysis",
|
||||||
|
Short: "static-analysis",
|
||||||
|
Long: `static-analysis runs static analysis of go code.`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
err := rootCmd.Execute()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(NewFeatureGatesCommand())
|
||||||
|
}
|
135
test/featuregates_linter/cmd/util.go
Normal file
135
test/featuregates_linter/cmd/util.go
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// env configs
|
||||||
|
GOOS string = findGOOS()
|
||||||
|
)
|
||||||
|
|
||||||
|
func findGOOS() string {
|
||||||
|
goCmd := exec.Command("go", "env", "GOOS")
|
||||||
|
out, err := goCmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("running `go env` failed: %v\n\n%s", err, string(out)))
|
||||||
|
}
|
||||||
|
if len(out) == 0 {
|
||||||
|
panic("empty result from `go env GOOS`")
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// identifierName returns the name of an identifier.
|
||||||
|
// if ignorePkg, only return the last part of the identifierName.
|
||||||
|
func identifierName(v ast.Expr, ignorePkg bool) string {
|
||||||
|
if id, ok := v.(*ast.Ident); ok {
|
||||||
|
return id.Name
|
||||||
|
}
|
||||||
|
if se, ok := v.(*ast.SelectorExpr); ok {
|
||||||
|
if ignorePkg {
|
||||||
|
return identifierName(se.Sel, ignorePkg)
|
||||||
|
}
|
||||||
|
return identifierName(se.X, ignorePkg) + "." + identifierName(se.Sel, ignorePkg)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// importAliasMap returns the mapping from pkg path to import alias.
|
||||||
|
func importAliasMap(imports []*ast.ImportSpec) map[string]string {
|
||||||
|
m := map[string]string{}
|
||||||
|
for _, im := range imports {
|
||||||
|
var importAlias string
|
||||||
|
if im.Name == nil {
|
||||||
|
pathSegments := strings.Split(im.Path.Value, "/")
|
||||||
|
importAlias = strings.Trim(pathSegments[len(pathSegments)-1], "\"")
|
||||||
|
} else {
|
||||||
|
importAlias = im.Name.String()
|
||||||
|
}
|
||||||
|
m[im.Path.Value] = importAlias
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func basicStringLiteral(v ast.Expr) (string, error) {
|
||||||
|
bl, ok := v.(*ast.BasicLit)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("cannot parse a non BasicLit to basicStringLiteral")
|
||||||
|
}
|
||||||
|
|
||||||
|
if bl.Kind != token.STRING {
|
||||||
|
return "", fmt.Errorf("cannot parse a non STRING token to basicStringLiteral")
|
||||||
|
}
|
||||||
|
return strings.Trim(bl.Value, `"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func basicIntLiteral(v ast.Expr) (int64, error) {
|
||||||
|
bl, ok := v.(*ast.BasicLit)
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("cannot parse a non BasicLit to basicIntLiteral")
|
||||||
|
}
|
||||||
|
|
||||||
|
if bl.Kind != token.INT {
|
||||||
|
return 0, fmt.Errorf("cannot parse a non INT token to basicIntLiteral")
|
||||||
|
}
|
||||||
|
value, err := strconv.ParseInt(bl.Value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBool(variables map[string]ast.Expr, v ast.Expr) (bool, error) {
|
||||||
|
ident := identifierName(v, false)
|
||||||
|
switch ident {
|
||||||
|
case "true":
|
||||||
|
return true, nil
|
||||||
|
case "false":
|
||||||
|
return false, nil
|
||||||
|
default:
|
||||||
|
if varVal, ok := variables[ident]; ok {
|
||||||
|
return parseBool(variables, varVal)
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("cannot parse %s into bool", ident)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func globalVariableDeclarations(tree *ast.File) map[string]ast.Expr {
|
||||||
|
consts := make(map[string]ast.Expr)
|
||||||
|
for _, d := range tree.Decls {
|
||||||
|
if gd, ok := d.(*ast.GenDecl); ok && (gd.Tok == token.CONST || gd.Tok == token.VAR) {
|
||||||
|
for _, spec := range gd.Specs {
|
||||||
|
if vspec, ok := spec.(*ast.ValueSpec); ok {
|
||||||
|
for _, name := range vspec.Names {
|
||||||
|
for _, value := range vspec.Values {
|
||||||
|
consts[name.Name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consts
|
||||||
|
}
|
23
test/featuregates_linter/main.go
Normal file
23
test/featuregates_linter/main.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 main
|
||||||
|
|
||||||
|
import "k8s.io/kubernetes/test/featuregates_linter/cmd"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
9
test/featuregates_linter/test_data/OWNERS
Normal file
9
test/featuregates_linter/test_data/OWNERS
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# See the OWNERS docs at https://go.k8s.io/owners
|
||||||
|
|
||||||
|
# Changing feature lifecycle requires feature-approvers approval
|
||||||
|
options:
|
||||||
|
no_parent_owners: true
|
||||||
|
approvers:
|
||||||
|
- feature-approvers
|
||||||
|
labels:
|
||||||
|
- area/feature-gates
|
972
test/featuregates_linter/test_data/unversioned_feature_list.yaml
Normal file
972
test/featuregates_linter/test_data/unversioned_feature_list.yaml
Normal file
@ -0,0 +1,972 @@
|
|||||||
|
- name: AdmissionWebhookMatchConditions
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: AggregatedDiscoveryEndpoint
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: AllowDNSOnlyNodeCSR
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Deprecated
|
||||||
|
version: ""
|
||||||
|
- name: AllowInsecureKubeletCertificateSigningRequests
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Deprecated
|
||||||
|
version: ""
|
||||||
|
- name: AllowServiceLBStatusOnNonLB
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Deprecated
|
||||||
|
version: ""
|
||||||
|
- name: AnonymousAuthConfigurableEndpoints
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: AnyVolumeDataSource
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: APIListChunking
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: APIResponseCompression
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: APIServerIdentity
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: APIServerTracing
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: APIServingWithRoutine
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: AppArmor
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: AppArmorFields
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: AuthorizeNodeWithSelectors
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: AuthorizeWithSelectors
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: CloudControllerManagerWebhook
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: CloudDualStackNodeIPs
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: ClusterTrustBundle
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: ClusterTrustBundleProjection
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: ComponentSLIs
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ConcurrentWatchObjectDecode
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ConsistentListFromCache
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ContainerCheckpoint
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ContextualLogging
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: CoordinatedLeaderElection
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: CPUCFSQuotaPeriod
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: CPUManager
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: CPUManagerPolicyAlphaOptions
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: CPUManagerPolicyBetaOptions
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: CPUManagerPolicyOptions
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: CRDValidationRatcheting
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: CronJobsScheduledAnnotation
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: CrossNamespaceVolumeDataSource
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: CSIMigrationPortworx
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: CSIVolumeHealth
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: CustomResourceFieldSelectors
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: DevicePluginCDIDevices
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: DisableAllocatorDualWrite
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: DisableCloudProviders
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: DisableKubeletCloudCredentialProviders
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: DisableNodeKubeProxyVersion
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: DRAControlPlaneController
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: DynamicResourceAllocation
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: EfficientWatchResumption
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: ElasticIndexedJob
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: EventedPLEG
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: ExecProbeTimeout
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: GracefulNodeShutdown
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: GracefulNodeShutdownBasedOnPodPriority
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: HonorPVReclaimPolicy
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: HPAContainerMetrics
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: HPAScaleToZero
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: ImageMaximumGCAge
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ImageVolume
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: InPlacePodVerticalScaling
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: InTreePluginPortworxUnregister
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: JobBackoffLimitPerIndex
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: JobManagedBy
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: JobPodFailurePolicy
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: JobPodReplacementPolicy
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: JobSuccessPolicy
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: KMSv1
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Deprecated
|
||||||
|
version: ""
|
||||||
|
- name: KMSv2
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: KMSv2KDF
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: KubeletCgroupDriverFromCRI
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: KubeletInUserNamespace
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: KubeletPodResourcesDynamicResources
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: KubeletPodResourcesGet
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: KubeletSeparateDiskGC
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: KubeletTracing
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: KubeProxyDrainingTerminatingNodes
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: LegacyServiceAccountTokenCleanUp
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: LoadBalancerIPMode
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: LocalStorageCapacityIsolationFSQuotaMonitoring
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: LogarithmicScaleDown
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: LoggingAlphaOptions
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: LoggingBetaOptions
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: MatchLabelKeysInPodAffinity
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: MatchLabelKeysInPodTopologySpread
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: MaxUnavailableStatefulSet
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: MemoryManager
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: MemoryQoS
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: MinDomainsInPodTopologySpread
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: MultiCIDRServiceAllocator
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: MutatingAdmissionPolicy
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: NewVolumeManagerReconstruction
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: NFTablesProxyMode
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: NodeInclusionPolicyInPodTopologySpread
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: NodeLogQuery
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: NodeOutOfServiceVolumeDetach
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: NodeSwap
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: OpenAPIEnums
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: PDBUnhealthyPodEvictionPolicy
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: PersistentVolumeLastPhaseTransitionTime
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: PodAndContainerStatsFromCRI
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: PodDeletionCost
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: PodDisruptionConditions
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: PodHostIPs
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: PodIndexLabel
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: PodLifecycleSleepAction
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: PodReadyToStartContainersCondition
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: PodSchedulingReadiness
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: PortForwardWebsockets
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ProcMountType
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: QOSReserved
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: RecoverVolumeExpansionFailure
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: RecursiveReadOnlyMounts
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: RelaxedEnvironmentVariableValidation
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: ReloadKubeletServerCertificateFile
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: RemainingItemCount
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: ResilientWatchCacheInitialization
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ResourceHealthStatus
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: RetryGenerateName
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: RotateKubeletServerCertificate
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: RuntimeClassInImageCriAPI
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: SchedulerQueueingHints
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: SELinuxMount
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: SELinuxMountReadWriteOncePod
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: SeparateCacheWatchRPC
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: SeparateTaintEvictionController
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ServerSideApply
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: ServerSideFieldValidation
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: ServiceAccountTokenJTI
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ServiceAccountTokenNodeBinding
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ServiceAccountTokenNodeBindingValidation
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ServiceAccountTokenPodNodeInfo
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ServiceTrafficDistribution
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: SidecarContainers
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: SizeMemoryBackedVolumes
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: StableLoadBalancerNodeSet
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: StatefulSetAutoDeletePVC
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: StatefulSetStartOrdinal
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: StorageNamespaceIndex
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: StorageVersionAPI
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: StorageVersionHash
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: StorageVersionMigrator
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: StrictCostEnforcementForVAP
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: StrictCostEnforcementForWebhooks
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: StructuredAuthenticationConfiguration
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: StructuredAuthorizationConfiguration
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: SupplementalGroupsPolicy
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: TopologyAwareHints
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: TopologyManagerPolicyAlphaOptions
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: TopologyManagerPolicyBetaOptions
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: TopologyManagerPolicyOptions
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: TranslateStreamCloseWebsocketRequests
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: UnauthenticatedHTTP2DOSMitigation
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: UnknownVersionInteroperabilityProxy
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: UserNamespacesPodSecurityStandards
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: UserNamespacesSupport
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ValidatingAdmissionPolicy
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: VolumeAttributesClass
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: VolumeCapacityPriority
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: WatchBookmark
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
||||||
|
- name: WatchCacheInitializationPostStartHook
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: WatchFromStorageWithoutResourceVersion
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: WatchList
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: WindowsHostNetwork
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: WinDSR
|
||||||
|
versionedSpecs:
|
||||||
|
- default: false
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Alpha
|
||||||
|
version: ""
|
||||||
|
- name: WinOverlay
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: false
|
||||||
|
preRelease: Beta
|
||||||
|
version: ""
|
||||||
|
- name: ZeroLimitedNominalConcurrencyShares
|
||||||
|
versionedSpecs:
|
||||||
|
- default: true
|
||||||
|
lockToDefault: true
|
||||||
|
preRelease: GA
|
||||||
|
version: ""
|
@ -0,0 +1 @@
|
|||||||
|
[]
|
Loading…
Reference in New Issue
Block a user