1/* 2Copyright 2017 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package features 18 19import ( 20 "fmt" 21 "sort" 22 "strconv" 23 "strings" 24 25 "k8s.io/apimachinery/pkg/util/version" 26 "k8s.io/component-base/featuregate" 27 28 "github.com/pkg/errors" 29) 30 31const ( 32 // IPv6DualStack is expected to be beta in v1.21 33 IPv6DualStack = "IPv6DualStack" 34 // PublicKeysECDSA is expected to be alpha in v1.19 35 PublicKeysECDSA = "PublicKeysECDSA" 36 // RootlessControlPlane is expected to be in alpha in v1.22 37 RootlessControlPlane = "RootlessControlPlane" 38) 39 40// InitFeatureGates are the default feature gates for the init command 41var InitFeatureGates = FeatureList{ 42 IPv6DualStack: {FeatureSpec: featuregate.FeatureSpec{Default: true, PreRelease: featuregate.Beta}}, 43 PublicKeysECDSA: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}}, 44 RootlessControlPlane: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}}, 45} 46 47// Feature represents a feature being gated 48type Feature struct { 49 featuregate.FeatureSpec 50 MinimumVersion *version.Version 51 HiddenInHelpText bool 52 DeprecationMessage string 53} 54 55// FeatureList represents a list of feature gates 56type FeatureList map[string]Feature 57 58// ValidateVersion ensures that a feature gate list is compatible with the chosen Kubernetes version 59func ValidateVersion(allFeatures FeatureList, requestedFeatures map[string]bool, requestedVersion string) error { 60 if requestedVersion == "" { 61 return nil 62 } 63 parsedExpVersion, err := version.ParseSemantic(requestedVersion) 64 if err != nil { 65 return errors.Wrapf(err, "error parsing version %s", requestedVersion) 66 } 67 for k := range requestedFeatures { 68 if minVersion := allFeatures[k].MinimumVersion; minVersion != nil { 69 if !parsedExpVersion.AtLeast(minVersion) { 70 return errors.Errorf( 71 "the requested Kubernetes version (%s) is incompatible with the %s feature gate, which needs %s as a minimum", 72 requestedVersion, k, minVersion) 73 } 74 } 75 } 76 return nil 77} 78 79// Enabled indicates whether a feature name has been enabled 80func Enabled(featureList map[string]bool, featureName string) bool { 81 if enabled, ok := featureList[string(featureName)]; ok { 82 return enabled 83 } 84 return InitFeatureGates[string(featureName)].Default 85} 86 87// Supports indicates whether a feature name is supported on the given 88// feature set 89func Supports(featureList FeatureList, featureName string) bool { 90 for k, v := range featureList { 91 if featureName == string(k) { 92 return v.PreRelease != featuregate.Deprecated 93 } 94 } 95 return false 96} 97 98// Keys returns a slice of feature names for a given feature set 99func Keys(featureList FeatureList) []string { 100 var list []string 101 for k := range featureList { 102 list = append(list, string(k)) 103 } 104 return list 105} 106 107// KnownFeatures returns a slice of strings describing the FeatureList features. 108func KnownFeatures(f *FeatureList) []string { 109 var known []string 110 for k, v := range *f { 111 if v.HiddenInHelpText { 112 continue 113 } 114 115 pre := "" 116 if v.PreRelease != featuregate.GA { 117 pre = fmt.Sprintf("%s - ", v.PreRelease) 118 } 119 known = append(known, fmt.Sprintf("%s=true|false (%sdefault=%t)", k, pre, v.Default)) 120 } 121 sort.Strings(known) 122 return known 123} 124 125// NewFeatureGate parses a string of the form "key1=value1,key2=value2,..." into a 126// map[string]bool of known keys or returns an error. 127func NewFeatureGate(f *FeatureList, value string) (map[string]bool, error) { 128 featureGate := map[string]bool{} 129 for _, s := range strings.Split(value, ",") { 130 if len(s) == 0 { 131 continue 132 } 133 134 arr := strings.SplitN(s, "=", 2) 135 if len(arr) != 2 { 136 return nil, errors.Errorf("missing bool value for feature-gate key:%s", s) 137 } 138 139 k := strings.TrimSpace(arr[0]) 140 v := strings.TrimSpace(arr[1]) 141 142 featureSpec, ok := (*f)[k] 143 if !ok { 144 return nil, errors.Errorf("unrecognized feature-gate key: %s", k) 145 } 146 147 if featureSpec.PreRelease == featuregate.Deprecated { 148 return nil, errors.Errorf("feature-gate key is deprecated: %s", k) 149 } 150 151 boolValue, err := strconv.ParseBool(v) 152 if err != nil { 153 return nil, errors.Errorf("invalid value %v for feature-gate key: %s, use true|false instead", v, k) 154 } 155 featureGate[k] = boolValue 156 } 157 158 return featureGate, nil 159} 160 161// CheckDeprecatedFlags takes a list of existing feature gate flags and validates against the current feature flag set. 162// It used during upgrades for ensuring consistency of feature gates used in an existing cluster, that might 163// be created with a previous version of kubeadm, with the set of features currently supported by kubeadm 164func CheckDeprecatedFlags(f *FeatureList, features map[string]bool) map[string]string { 165 deprecatedMsg := map[string]string{} 166 for k := range features { 167 featureSpec, ok := (*f)[k] 168 if !ok { 169 // This case should never happen, it is implemented only as a sentinel 170 // for removal of flags executed when flags are still in use (always before deprecate, then after one cycle remove) 171 deprecatedMsg[k] = fmt.Sprintf("Unknown feature gate flag: %s", k) 172 } 173 174 if featureSpec.PreRelease == featuregate.Deprecated { 175 if _, ok := deprecatedMsg[k]; !ok { 176 deprecatedMsg[k] = featureSpec.DeprecationMessage 177 } 178 } 179 } 180 181 return deprecatedMsg 182} 183