From 3331183daa265e597422e4d6c963d8fd6afa64ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Wed, 30 May 2018 09:23:46 +0300 Subject: [PATCH 1/2] kubeadm: Add a 'kubeadm config migrate' command --- cmd/kubeadm/app/cmd/config.go | 69 +++++++++++++++++++++++++++++++++ cmd/kubeadm/app/util/marshal.go | 39 +++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/cmd/kubeadm/app/cmd/config.go b/cmd/kubeadm/app/cmd/config.go index 62f89e92788..fc0277c482a 100644 --- a/cmd/kubeadm/app/cmd/config.go +++ b/cmd/kubeadm/app/cmd/config.go @@ -19,6 +19,7 @@ package cmd import ( "fmt" "io" + "io/ioutil" "strings" "github.com/golang/glog" @@ -30,6 +31,7 @@ import ( clientset "k8s.io/client-go/kubernetes" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" + kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" "k8s.io/kubernetes/cmd/kubeadm/app/constants" @@ -43,6 +45,7 @@ import ( ) const ( + // TODO: Figure out how to get these constants from the API machinery masterConfig = "MasterConfiguration" nodeConfig = "NodeConfiguration" sillyToken = "abcdef.0123456789abcdef" @@ -74,6 +77,7 @@ func NewCmdConfig(out io.Writer) *cobra.Command { cmd.PersistentFlags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster.") cmd.AddCommand(NewCmdConfigPrintDefault(out)) + cmd.AddCommand(NewCmdConfigMigrate(out)) cmd.AddCommand(NewCmdConfigUpload(out, &kubeConfigFile)) cmd.AddCommand(NewCmdConfigView(out, &kubeConfigFile)) cmd.AddCommand(NewCmdConfigImages(out)) @@ -138,6 +142,71 @@ func getDefaultAPIObjectBytes(apiObject string) ([]byte, error) { return []byte{}, fmt.Errorf("--api-object needs to be one of %v", availableAPIObjects) } +// NewCmdConfigMigrate returns cobra.Command for "kubeadm config migrate" command +func NewCmdConfigMigrate(out io.Writer) *cobra.Command { + var oldCfgPath, newCfgPath string + cmd := &cobra.Command{ + Use: "migrate", + Short: "Read an older version of the kubeadm configuration API types from a file, and output the similar config object for the newer version.", + Long: fmt.Sprintf(dedent.Dedent(` + This command lets you convert configuration objects of older versions to the latest supported version, + locally in the CLI tool without ever touching anything in the cluster. + In this version of kubeadm, the following API versions are supported: + - %s + - %s + + Further, kubeadm can only write out config of version %q, but read both types. + So regardless of what version you pass to the --old-config parameter here, the API object will be + read, deserialized, defaulted, converted, validated, and re-serialized when written to stdout or + --new-config if specified. + + In other words, the output of this command is what kubeadm actually would read internally if you + submitted this file to "kubeadm init" + `), kubeadmapiv1alpha2.SchemeGroupVersion.String(), kubeadmapiv1alpha1.SchemeGroupVersion.String(), kubeadmapiv1alpha2.SchemeGroupVersion.String()), + Run: func(cmd *cobra.Command, args []string) { + if len(oldCfgPath) == 0 { + kubeadmutil.CheckErr(fmt.Errorf("The --old-config flag is mandatory")) + } + + b, err := ioutil.ReadFile(oldCfgPath) + kubeadmutil.CheckErr(err) + + var outputBytes []byte + gvk, err := kubeadmutil.GroupVersionKindFromBytes(b, kubeadmscheme.Codecs) + kubeadmutil.CheckErr(err) + + switch gvk.Kind { + case masterConfig: + internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(oldCfgPath, &kubeadmapiv1alpha2.MasterConfiguration{}) + kubeadmutil.CheckErr(err) + + outputBytes, err = kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha2.SchemeGroupVersion, kubeadmscheme.Codecs) + kubeadmutil.CheckErr(err) + case nodeConfig: + internalcfg, err := configutil.NodeConfigFileAndDefaultsToInternalConfig(oldCfgPath, &kubeadmapiv1alpha2.NodeConfiguration{}) + kubeadmutil.CheckErr(err) + + // TODO: In the future we might not want to duplicate these two lines of code for every case here. + outputBytes, err = kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha2.SchemeGroupVersion, kubeadmscheme.Codecs) + kubeadmutil.CheckErr(err) + default: + kubeadmutil.CheckErr(fmt.Errorf("Didn't recognize type with GroupVersionKind: %v", gvk)) + } + + if newCfgPath == "" { + fmt.Fprintf(out, string(outputBytes)) + } else { + if err := ioutil.WriteFile(newCfgPath, outputBytes, 0644); err != nil { + kubeadmutil.CheckErr(fmt.Errorf("failed to write the new configuration to the file %q: %v", newCfgPath, err)) + } + } + }, + } + cmd.Flags().StringVar(&oldCfgPath, "old-config", "", "Path to the kubeadm config file that is using an old API version and should be converted. This flag is mandatory.") + cmd.Flags().StringVar(&newCfgPath, "new-config", "", "Path to the resulting equivalent kubeadm config file using the new API version. Optional, if not specified output will be sent to STDOUT.") + return cmd +} + // NewCmdConfigUpload returns cobra.Command for "kubeadm config upload" command func NewCmdConfigUpload(out io.Writer, kubeConfigFile *string) *cobra.Command { cmd := &cobra.Command{ diff --git a/cmd/kubeadm/app/util/marshal.go b/cmd/kubeadm/app/util/marshal.go index 76bd2ea7f62..aa201cabb7b 100644 --- a/cmd/kubeadm/app/util/marshal.go +++ b/cmd/kubeadm/app/util/marshal.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" clientsetscheme "k8s.io/client-go/kubernetes/scheme" + kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" ) // MarshalToYaml marshals an object into yaml. @@ -34,6 +35,8 @@ func MarshalToYaml(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) { } // MarshalToYamlForCodecs marshals an object into yaml using the specified codec +// TODO: Is specifying the gv really needed here? +// TODO: Can we support json out of the box easily here? func MarshalToYamlForCodecs(obj runtime.Object, gv schema.GroupVersion, codecs serializer.CodecFactory) ([]byte, error) { mediaType := "application/yaml" info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType) @@ -51,6 +54,8 @@ func UnmarshalFromYaml(buffer []byte, gv schema.GroupVersion) (runtime.Object, e } // UnmarshalFromYamlForCodecs unmarshals yaml into an object using the specified codec +// TODO: Is specifying the gv really needed here? +// TODO: Can we support json out of the box easily here? func UnmarshalFromYamlForCodecs(buffer []byte, gv schema.GroupVersion, codecs serializer.CodecFactory) (runtime.Object, error) { mediaType := "application/yaml" info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType) @@ -62,6 +67,40 @@ func UnmarshalFromYamlForCodecs(buffer []byte, gv schema.GroupVersion, codecs se return runtime.Decode(decoder, buffer) } +// GroupVersionKindFromBytes parses the bytes and returns the gvk +func GroupVersionKindFromBytes(buffer []byte, codecs serializer.CodecFactory) (schema.GroupVersionKind, error) { + + decoded, err := LoadYAML(buffer) + if err != nil { + return schema.EmptyObjectKind.GroupVersionKind(), fmt.Errorf("unable to decode config from bytes: %v", err) + } + kindStr, apiVersionStr := "", "" + + // As there was a bug in kubeadm v1.10 and earlier that made the YAML uploaded to the cluster configmap NOT have metav1.TypeMeta information + // we need to populate this here manually. If kind or apiVersion is empty, we know the apiVersion is v1alpha1, as by the time kubeadm had this bug, + // it could only write + // TODO: Remove this "hack" in v1.12 when we know the ConfigMap always contains v1alpha2 content written by kubeadm v1.11. Also, we will drop support for + // v1alpha1 in v1.12 + kind := decoded["kind"] + apiVersion := decoded["apiVersion"] + if kind == nil || len(kind.(string)) == 0 { + kindStr = "MasterConfiguration" + } else { + kindStr = kind.(string) + } + if apiVersion == nil || len(apiVersion.(string)) == 0 { + apiVersionStr = kubeadmapiv1alpha1.SchemeGroupVersion.String() + } else { + apiVersionStr = apiVersion.(string) + } + gv, err := schema.ParseGroupVersion(apiVersionStr) + if err != nil { + return schema.EmptyObjectKind.GroupVersionKind(), fmt.Errorf("unable to parse apiVersion: %v", err) + } + + return gv.WithKind(kindStr), nil +} + // LoadYAML is a small wrapper around go-yaml that ensures all nested structs are map[string]interface{} instead of map[interface{}]interface{}. func LoadYAML(bytes []byte) (map[string]interface{}, error) { var decoded map[interface{}]interface{} From 7914dce526a56347cdafcc239487a4a85f9e5e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Wed, 30 May 2018 09:29:03 +0300 Subject: [PATCH 2/2] autogenerated --- cmd/kubeadm/app/cmd/BUILD | 1 + cmd/kubeadm/app/util/BUILD | 1 + docs/.generated_docs | 2 ++ docs/admin/kubeadm_config_migrate.md | 3 +++ docs/man/man1/kubeadm-config-migrate.1 | 3 +++ 5 files changed, 10 insertions(+) create mode 100644 docs/admin/kubeadm_config_migrate.md create mode 100644 docs/man/man1/kubeadm-config-migrate.1 diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index a07a39e42ce..5ebe1029b11 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -22,6 +22,7 @@ go_library( deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library", + "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library", "//cmd/kubeadm/app/cmd/phases:go_default_library", diff --git a/cmd/kubeadm/app/util/BUILD b/cmd/kubeadm/app/util/BUILD index 181784908cd..245aac69737 100644 --- a/cmd/kubeadm/app/util/BUILD +++ b/cmd/kubeadm/app/util/BUILD @@ -20,6 +20,7 @@ go_library( importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//vendor/gopkg.in/yaml.v2:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", diff --git a/docs/.generated_docs b/docs/.generated_docs index 91926436747..f6b821fb3fb 100644 --- a/docs/.generated_docs +++ b/docs/.generated_docs @@ -61,6 +61,7 @@ docs/admin/kubeadm_config.md docs/admin/kubeadm_config_images.md docs/admin/kubeadm_config_images_list.md docs/admin/kubeadm_config_images_pull.md +docs/admin/kubeadm_config_migrate.md docs/admin/kubeadm_config_print-default.md docs/admin/kubeadm_config_upload.md docs/admin/kubeadm_config_upload_from-file.md @@ -140,6 +141,7 @@ docs/man/man1/kubeadm-completion.1 docs/man/man1/kubeadm-config-images-list.1 docs/man/man1/kubeadm-config-images-pull.1 docs/man/man1/kubeadm-config-images.1 +docs/man/man1/kubeadm-config-migrate.1 docs/man/man1/kubeadm-config-print-default.1 docs/man/man1/kubeadm-config-upload-from-file.1 docs/man/man1/kubeadm-config-upload-from-flags.1 diff --git a/docs/admin/kubeadm_config_migrate.md b/docs/admin/kubeadm_config_migrate.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_config_migrate.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-config-migrate.1 b/docs/man/man1/kubeadm-config-migrate.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-config-migrate.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file.