diff --git a/cmd/kubeadm/app/util/config/common.go b/cmd/kubeadm/app/util/config/common.go index eb005b2a38..9a8f4c0cab 100644 --- a/cmd/kubeadm/app/util/config/common.go +++ b/cmd/kubeadm/app/util/config/common.go @@ -27,6 +27,7 @@ import ( "k8s.io/klog" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" netutil "k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/version" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" @@ -48,15 +49,9 @@ func MarshalKubeadmConfigObject(obj runtime.Object) ([]byte, error) { } } -// DetectUnsupportedVersion reads YAML bytes, extracts the TypeMeta information and errors out with an user-friendly message if the API spec is too old for this kubeadm version -func DetectUnsupportedVersion(b []byte) error { - gvks, err := kubeadmutil.GroupVersionKindsFromBytes(b) - if err != nil { - return err - } - - // TODO: On our way to making the kubeadm API beta and higher, give good user output in case they use an old config file with a new kubeadm version, and - // tell them how to upgrade. The support matrix will look something like this now and in the future: +// ValidateSupportedVersion checks if a supplied GroupVersion is not on the list of unsupported GVs. If it is, an error is returned. +func ValidateSupportedVersion(gv schema.GroupVersion) error { + // The support matrix will look something like this now and in the future: // v1.10 and earlier: v1alpha1 // v1.11: v1alpha1 read-only, writes only v1alpha2 config // v1.12: v1alpha2 read-only, writes only v1alpha3 config. Warns if the user tries to use v1alpha1 @@ -65,27 +60,9 @@ func DetectUnsupportedVersion(b []byte) error { "kubeadm.k8s.io/v1alpha1": "v1.11", "kubeadm.k8s.io/v1alpha2": "v1.12", } - // If we find an old API version in this gvk list, error out and tell the user why this doesn't work - knownKinds := map[string]bool{} - for _, gvk := range gvks { - if useKubeadmVersion := oldKnownAPIVersions[gvk.GroupVersion().String()]; len(useKubeadmVersion) != 0 { - return errors.Errorf("your configuration file uses an old API spec: %q. Please use kubeadm %s instead and run 'kubeadm config migrate --old-config old.yaml --new-config new.yaml', which will write the new, similar spec using a newer API version.", gvk.GroupVersion().String(), useKubeadmVersion) - } - knownKinds[gvk.Kind] = true + if useKubeadmVersion := oldKnownAPIVersions[gv.String()]; useKubeadmVersion != "" { + return errors.Errorf("your configuration file uses an old API spec: %q. Please use kubeadm %s instead and run 'kubeadm config migrate --old-config old.yaml --new-config new.yaml', which will write the new, similar spec using a newer API version.", gv.String(), useKubeadmVersion) } - - // InitConfiguration and JoinConfiguration may not apply together, warn if more than one is specified - mutuallyExclusive := []string{constants.InitConfigurationKind, constants.JoinConfigurationKind} - mutuallyExclusiveCount := 0 - for _, kind := range mutuallyExclusive { - if knownKinds[kind] { - mutuallyExclusiveCount++ - } - } - if mutuallyExclusiveCount > 1 { - klog.Warningf("WARNING: Detected resource kinds that may not apply: %v", mutuallyExclusive) - } - return nil } diff --git a/cmd/kubeadm/app/util/config/common_test.go b/cmd/kubeadm/app/util/config/common_test.go index 405da84728..e0d18dda99 100644 --- a/cmd/kubeadm/app/util/config/common_test.go +++ b/cmd/kubeadm/app/util/config/common_test.go @@ -17,187 +17,66 @@ limitations under the License. package config import ( - "bytes" "io/ioutil" "os" "testing" "github.com/lithammer/dedent" + "k8s.io/apimachinery/pkg/runtime/schema" kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1" "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" ) -var files = map[string][]byte{ - "Master_v1alpha1": []byte(` -apiVersion: kubeadm.k8s.io/v1alpha1 -kind: InitConfiguration -`), - "Node_v1alpha1": []byte(` -apiVersion: kubeadm.k8s.io/v1alpha1 -kind: NodeConfiguration -`), - "Master_v1alpha2": []byte(` -apiVersion: kubeadm.k8s.io/v1alpha2 -kind: MasterConfiguration -`), - "Node_v1alpha2": []byte(` -apiVersion: kubeadm.k8s.io/v1alpha2 -kind: NodeConfiguration -`), - "Init_v1alpha3": []byte(` -apiVersion: kubeadm.k8s.io/v1alpha3 -kind: InitConfiguration -`), - "Join_v1alpha3": []byte(` -apiVersion: kubeadm.k8s.io/v1alpha3 -kind: JoinConfiguration -`), - "Init_v1beta1": []byte(` -apiVersion: kubeadm.k8s.io/v1beta1 -kind: InitConfiguration -`), - "Join_v1beta1": []byte(` -apiVersion: kubeadm.k8s.io/v1beta1 -kind: JoinConfiguration -`), - "NoKind": []byte(` -apiVersion: baz.k8s.io/v1 -foo: foo -bar: bar -`), - "NoAPIVersion": []byte(` -kind: Bar -foo: foo -bar: bar -`), - "Foo": []byte(` -apiVersion: foo.k8s.io/v1 -kind: Foo -`), -} +func TestValidateSupportedVersion(t *testing.T) { + const KubeadmGroupName = "kubeadm.k8s.io" -func TestDetectUnsupportedVersion(t *testing.T) { - var tests = []struct { - name string - fileContents []byte - expectedErr bool + tests := []struct { + gv schema.GroupVersion + expectedErr bool }{ { - name: "Master_v1alpha1", - fileContents: files["Master_v1alpha1"], - expectedErr: true, + gv: schema.GroupVersion{ + Group: KubeadmGroupName, + Version: "v1alpha1", + }, + expectedErr: true, }, { - name: "Node_v1alpha1", - fileContents: files["Node_v1alpha1"], - expectedErr: true, + gv: schema.GroupVersion{ + Group: KubeadmGroupName, + Version: "v1alpha2", + }, + expectedErr: true, }, { - name: "Master_v1alpha2", - fileContents: files["Master_v1alpha2"], - expectedErr: true, + gv: schema.GroupVersion{ + Group: KubeadmGroupName, + Version: "v1alpha3", + }, }, { - name: "Node_v1alpha2", - fileContents: files["Node_v1alpha2"], - expectedErr: true, + gv: schema.GroupVersion{ + Group: KubeadmGroupName, + Version: "v1beta1", + }, }, { - name: "Init_v1alpha3", - fileContents: files["Init_v1alpha3"], - }, - { - name: "Join_v1alpha3", - fileContents: files["Join_v1alpha3"], - }, - { - name: "Init_v1beta1", - fileContents: files["Init_v1beta1"], - }, - { - name: "Join_v1beta1", - fileContents: files["Join_v1beta1"], - }, - { - name: "DuplicateInit v1alpha3", - fileContents: bytes.Join([][]byte{files["Init_v1alpha3"], files["Init_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)), - expectedErr: true, - }, - { - name: "DuplicateInit v1beta1", - fileContents: bytes.Join([][]byte{files["Init_v1beta1"], files["Init_v1beta1"]}, []byte(constants.YAMLDocumentSeparator)), - expectedErr: true, - }, - { - name: "DuplicateInit v1beta1 and v1alpha3", - fileContents: bytes.Join([][]byte{files["Init_v1beta1"], files["Init_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)), - expectedErr: true, - }, - { - name: "DuplicateJoin v1alpha3", - fileContents: bytes.Join([][]byte{files["Join_v1alpha3"], files["Join_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)), - expectedErr: true, - }, - { - name: "DuplicateJoin v1beta1", - fileContents: bytes.Join([][]byte{files["Join_v1beta1"], files["Join_v1beta1"]}, []byte(constants.YAMLDocumentSeparator)), - expectedErr: true, - }, - { - name: "DuplicateJoin v1beta1 and v1alpha3", - fileContents: bytes.Join([][]byte{files["Join_v1beta1"], files["Join_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)), - expectedErr: true, - }, - { - name: "NoKind", - fileContents: files["NoKind"], - expectedErr: true, - }, - { - name: "NoAPIVersion", - fileContents: files["NoAPIVersion"], - expectedErr: true, - }, - { - name: "Ignore other Kind", - fileContents: bytes.Join([][]byte{files["Foo"], files["Master_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)), - }, - { - name: "Ignore other Kind", - fileContents: bytes.Join([][]byte{files["Foo"], files["Master_v1beta1"]}, []byte(constants.YAMLDocumentSeparator)), - }, - // CanMixInitJoin cases used to be MustNotMixInitJoin, however due to UX issues DetectUnsupportedVersion had to tolerate that. - // So the following tests actually verify, that Init and Join can be mixed together with no error. - { - name: "CanMixInitJoin v1alpha3", - fileContents: bytes.Join([][]byte{files["Init_v1alpha3"], files["Join_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)), - expectedErr: false, - }, - { - name: "CanMixInitJoin v1alpha3 - v1beta1", - fileContents: bytes.Join([][]byte{files["Init_v1alpha3"], files["Join_v1beta1"]}, []byte(constants.YAMLDocumentSeparator)), - expectedErr: false, - }, - { - name: "CanMixInitJoin v1beta1 - v1alpha3", - fileContents: bytes.Join([][]byte{files["Init_v1beta1"], files["Join_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)), - expectedErr: false, - }, - { - name: "CanMixInitJoin v1beta1", - fileContents: bytes.Join([][]byte{files["Init_v1beta1"], files["Join_v1beta1"]}, []byte(constants.YAMLDocumentSeparator)), - expectedErr: false, + gv: schema.GroupVersion{ + Group: "foo.k8s.io", + Version: "v1", + }, }, } for _, rt := range tests { - t.Run(rt.name, func(t2 *testing.T) { - - err := DetectUnsupportedVersion(rt.fileContents) - if (err != nil) != rt.expectedErr { - t2.Errorf("expected error: %t, actual: %t", rt.expectedErr, err != nil) + t.Run(rt.gv.String(), func(t *testing.T) { + err := ValidateSupportedVersion(rt.gv) + if rt.expectedErr && err == nil { + t.Error("unexpected success") + } else if !rt.expectedErr && err != nil { + t.Errorf("unexpected failure: %v", err) } }) } diff --git a/cmd/kubeadm/app/util/config/initconfiguration.go b/cmd/kubeadm/app/util/config/initconfiguration.go index 860188ac87..31d9ae7461 100644 --- a/cmd/kubeadm/app/util/config/initconfiguration.go +++ b/cmd/kubeadm/app/util/config/initconfiguration.go @@ -208,16 +208,17 @@ func BytesToInternalConfig(b []byte) (*kubeadmapi.InitConfiguration, error) { var clustercfg *kubeadmapi.ClusterConfiguration decodedComponentConfigObjects := map[componentconfigs.RegistrationKind]runtime.Object{} - if err := DetectUnsupportedVersion(b); err != nil { - return nil, err - } - gvkmap, err := kubeadmutil.SplitYAMLDocuments(b) if err != nil { return nil, err } for gvk, fileContent := range gvkmap { + // first, check if this GVK is supported one + if err := ValidateSupportedVersion(gvk.GroupVersion()); err != nil { + return nil, err + } + // verify the validity of the YAML strict.VerifyUnmarshalStrict(fileContent, gvk) diff --git a/cmd/kubeadm/app/util/config/joinconfiguration.go b/cmd/kubeadm/app/util/config/joinconfiguration.go index aa4d653075..34ad12ba35 100644 --- a/cmd/kubeadm/app/util/config/joinconfiguration.go +++ b/cmd/kubeadm/app/util/config/joinconfiguration.go @@ -72,10 +72,6 @@ func JoinConfigFileAndDefaultsToInternalConfig(cfgPath string, defaultversionedc return nil, errors.Wrapf(err, "unable to read config from %q ", cfgPath) } - if err := DetectUnsupportedVersion(b); err != nil { - return nil, err - } - gvkmap, err := kubeadmutil.SplitYAMLDocuments(b) if err != nil { return nil, err @@ -83,11 +79,20 @@ func JoinConfigFileAndDefaultsToInternalConfig(cfgPath string, defaultversionedc joinBytes := []byte{} for gvk, bytes := range gvkmap { - if gvk.Kind == constants.JoinConfigurationKind { - joinBytes = bytes - // verify the validity of the YAML - strict.VerifyUnmarshalStrict(bytes, gvk) + // not interested in anything other than JoinConfiguration + if gvk.Kind != constants.JoinConfigurationKind { + continue } + + // check if this version is supported one + if err := ValidateSupportedVersion(gvk.GroupVersion()); err != nil { + return nil, err + } + + // verify the validity of the YAML + strict.VerifyUnmarshalStrict(bytes, gvk) + + joinBytes = bytes } if len(joinBytes) == 0 {