mirror of https://github.com/k3s-io/k3s
kubeadm: Introduce ValidateSupportedVersion in place of DetectUnsupportedVersion
DetectUnsupportedVersion is somewhat uncomfortable, complex and inefficient function to use. It takes an entire YAML document as bytes, splits it up to byte slices of the different YAML sub-documents and group-version-kinds and searches through those to detect an unsupported kubeadm config. If such config is detected, the function returns an error, if it is not (i.e. the normal function operation) everything done so far is discarded. This could have been acceptable, if not the fact, that in all cases that this function is called, the YAML document bytes are split up and an iteration on GVK map is performed yet again. Hence, we don't need DetectUnsupportedVersion in its current form as it's inefficient, complex and takes only YAML document bytes. This change replaces DetectUnsupportedVersion with ValidateSupportedVersion, which takes a GroupVersion argument and checks if it is on the list of unsupported config versions. In that case an error is returned. ValidateSupportedVersion relies on the caller to read and split the YAML document and then iterate on its GVK map checking if the particular GroupVersion is supported or not. Signed-off-by: Rostislav M. Georgiev <rostislavg@vmware.com>pull/564/head
parent
2d6834b539
commit
e10dcf07d7
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue