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