diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 1816b273410..afe0fc12dfa 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -231,6 +231,10 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight, return nil, err } + if err := features.ValidateVersion(features.InitFeatureGates, cfg.FeatureGates, cfg.KubernetesVersion); err != nil { + return nil, err + } + fmt.Printf("[init] Using Kubernetes version: %s\n", cfg.KubernetesVersion) fmt.Printf("[init] Using Authorization modes: %v\n", cfg.AuthorizationModes) diff --git a/cmd/kubeadm/app/features/BUILD b/cmd/kubeadm/app/features/BUILD index 147dac3bc69..3bf69a4b2b9 100644 --- a/cmd/kubeadm/app/features/BUILD +++ b/cmd/kubeadm/app/features/BUILD @@ -10,7 +10,10 @@ go_library( name = "go_default_library", srcs = ["features.go"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/features", - deps = ["//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library"], + deps = [ + "//pkg/util/version:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], ) filegroup( diff --git a/cmd/kubeadm/app/features/features.go b/cmd/kubeadm/app/features/features.go index faab302eb6a..422c9dd78f5 100644 --- a/cmd/kubeadm/app/features/features.go +++ b/cmd/kubeadm/app/features/features.go @@ -23,21 +23,61 @@ import ( "strings" utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/util/version" ) const ( + // HighAvailability is alpha in v1.9 + HighAvailability = "HighAvailability" + // SelfHosting is beta in v1.8 - SelfHosting utilfeature.Feature = "SelfHosting" + SelfHosting = "SelfHosting" // StoreCertsInSecrets is alpha in v1.8 - StoreCertsInSecrets utilfeature.Feature = "StoreCertsInSecrets" + StoreCertsInSecrets = "StoreCertsInSecrets" ) +var v190 = version.MustParseSemantic("v1.9.0") + +// InitFeatureGates are the default feature gates for the init command +var InitFeatureGates = FeatureList{ + SelfHosting: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Beta}}, + StoreCertsInSecrets: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}}, + HighAvailability: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, +} + +// Feature represents a feature being gated +type Feature struct { + utilfeature.FeatureSpec + MinimumVersion *version.Version +} + // FeatureList represents a list of feature gates -type FeatureList map[utilfeature.Feature]utilfeature.FeatureSpec +type FeatureList map[string]Feature + +// ValidateVersion ensures that a feature gate list is compatible with the chosen kubernetes version +func ValidateVersion(allFeatures FeatureList, requestedFeatures map[string]bool, requestedVersion string) error { + if requestedVersion == "" { + return nil + } + parsedExpVersion, err := version.ParseSemantic(requestedVersion) + if err != nil { + return fmt.Errorf("Error parsing version %s: %v", requestedVersion, err) + } + for k := range requestedFeatures { + if minVersion := allFeatures[k].MinimumVersion; minVersion != nil { + if !parsedExpVersion.AtLeast(minVersion) { + return fmt.Errorf( + "the requested kubernetes version (%s) is incompatible with the %s feature gate, which needs %s as a minimum", + requestedVersion, k, minVersion) + } + } + } + return nil +} // Enabled indicates whether a feature name has been enabled -func Enabled(featureList map[string]bool, featureName utilfeature.Feature) bool { +func Enabled(featureList map[string]bool, featureName string) bool { return featureList[string(featureName)] } @@ -61,12 +101,6 @@ func Keys(featureList FeatureList) []string { return list } -// InitFeatureGates are the default feature gates for the init command -var InitFeatureGates = FeatureList{ - SelfHosting: {Default: false, PreRelease: utilfeature.Alpha}, - StoreCertsInSecrets: {Default: false, PreRelease: utilfeature.Alpha}, -} - // KnownFeatures returns a slice of strings describing the FeatureList features. func KnownFeatures(f *FeatureList) []string { var known []string diff --git a/cmd/kubeadm/app/features/features_test.go b/cmd/kubeadm/app/features/features_test.go index 486f10f84fc..c761452c452 100644 --- a/cmd/kubeadm/app/features/features_test.go +++ b/cmd/kubeadm/app/features/features_test.go @@ -25,9 +25,9 @@ import ( func TestKnownFeatures(t *testing.T) { var someFeatures = FeatureList{ - "feature2": {Default: true, PreRelease: utilfeature.Alpha}, - "feature1": {Default: false, PreRelease: utilfeature.Beta}, - "feature3": {Default: false, PreRelease: utilfeature.GA}, + "feature2": {FeatureSpec: utilfeature.FeatureSpec{Default: true, PreRelease: utilfeature.Alpha}}, + "feature1": {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Beta}}, + "feature3": {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.GA}}, } r := KnownFeatures(&someFeatures) @@ -55,8 +55,8 @@ func TestKnownFeatures(t *testing.T) { func TestNewFeatureGate(t *testing.T) { var someFeatures = FeatureList{ - "feature1": {Default: false, PreRelease: utilfeature.Beta}, - "feature2": {Default: true, PreRelease: utilfeature.Alpha}, + "feature1": {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Beta}}, + "feature2": {FeatureSpec: utilfeature.FeatureSpec{Default: true, PreRelease: utilfeature.Alpha}}, } var tests = []struct { @@ -117,3 +117,42 @@ func TestNewFeatureGate(t *testing.T) { } } } + +func TestValidateVersion(t *testing.T) { + var someFeatures = FeatureList{ + "feature1": {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Beta}}, + "feature2": {FeatureSpec: utilfeature.FeatureSpec{Default: true, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, + } + + var tests = []struct { + requestedVersion string + requestedFeatures map[string]bool + expectedError bool + }{ + { //no min version + requestedFeatures: map[string]bool{"feature1": true}, + expectedError: false, + }, + { //min version but correct value given + requestedFeatures: map[string]bool{"feature2": true}, + requestedVersion: "v1.9.0", + expectedError: false, + }, + { //min version and incorrect value given + requestedFeatures: map[string]bool{"feature2": true}, + requestedVersion: "v1.8.2", + expectedError: true, + }, + } + + for _, test := range tests { + err := ValidateVersion(someFeatures, test.requestedFeatures, test.requestedVersion) + if !test.expectedError && err != nil { + t.Errorf("ValidateVersion failed when not expected: %v", err) + continue + } else if test.expectedError && err == nil { + t.Error("ValidateVersion didn't failed when expected") + continue + } + } +}