From 0774f779d5cc7beb9ec920bc0cfb285ce294c85f Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Thu, 22 Jun 2023 14:18:34 +0300 Subject: [PATCH 1/2] kubeadm: add v1beta4 to the kubeadm API scheme The highest priority is still v1beta3. --- cmd/kubeadm/app/apis/kubeadm/scheme/scheme.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/kubeadm/app/apis/kubeadm/scheme/scheme.go b/cmd/kubeadm/app/apis/kubeadm/scheme/scheme.go index 19d691f9db43..332fdd4ed836 100644 --- a/cmd/kubeadm/app/apis/kubeadm/scheme/scheme.go +++ b/cmd/kubeadm/app/apis/kubeadm/scheme/scheme.go @@ -25,6 +25,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta4" ) // Scheme is the runtime.Scheme to which all kubeadm api types are registered. @@ -42,5 +43,8 @@ func init() { func AddToScheme(scheme *runtime.Scheme) { utilruntime.Must(kubeadm.AddToScheme(scheme)) utilruntime.Must(v1beta3.AddToScheme(scheme)) - utilruntime.Must(scheme.SetVersionPriority(v1beta3.SchemeGroupVersion)) + utilruntime.Must(v1beta4.AddToScheme(scheme)) + // TODO: https://github.com/kubernetes/kubeadm/issues/2890 + // make v1beta4 highest priority + utilruntime.Must(scheme.SetVersionPriority(v1beta3.SchemeGroupVersion, v1beta4.SchemeGroupVersion)) } From f04484fe77561d0cacba9548c8b8d0db71ba152d Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Thu, 22 Jun 2023 14:13:39 +0300 Subject: [PATCH 2/2] kubeadm: add the --allow-experimental-api flag to "config" commands Add the flag --allow-experimental-api to the "config migrate" and "config validate" commands. The flag allows validating / migrating-to a unreleased / experimental API version. Add a new experimentalAPIVersions map in validateSupportedVersion() that contains v1beta4. --- cmd/kubeadm/app/cmd/config.go | 9 ++++-- cmd/kubeadm/app/cmd/options/constant.go | 3 ++ cmd/kubeadm/app/util/config/common.go | 28 +++++++++++++------ cmd/kubeadm/app/util/config/common_test.go | 23 ++++++++++++--- .../app/util/config/initconfiguration.go | 6 ++-- .../app/util/config/joinconfiguration.go | 6 ++-- 6 files changed, 55 insertions(+), 20 deletions(-) diff --git a/cmd/kubeadm/app/cmd/config.go b/cmd/kubeadm/app/cmd/config.go index 018421fa74fb..6bba4e08606c 100644 --- a/cmd/kubeadm/app/cmd/config.go +++ b/cmd/kubeadm/app/cmd/config.go @@ -225,6 +225,8 @@ func getDefaultNodeConfigBytes() ([]byte, error) { // newCmdConfigMigrate returns cobra.Command for "kubeadm config migrate" command func newCmdConfigMigrate(out io.Writer) *cobra.Command { var oldCfgPath, newCfgPath string + var allowExperimental bool + 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", @@ -252,7 +254,7 @@ func newCmdConfigMigrate(out io.Writer) *cobra.Command { return err } - outputBytes, err := configutil.MigrateOldConfig(oldCfgBytes) + outputBytes, err := configutil.MigrateOldConfig(oldCfgBytes, allowExperimental) if err != nil { return err } @@ -270,12 +272,14 @@ func newCmdConfigMigrate(out io.Writer) *cobra.Command { } 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.") + cmd.Flags().BoolVar(&allowExperimental, options.AllowExperimentalAPI, false, "Allow migration to experimental, unreleased APIs.") return cmd } // newCmdConfigValidate returns cobra.Command for the "kubeadm config validate" command func newCmdConfigValidate(out io.Writer) *cobra.Command { var cfgPath string + var allowExperimental bool cmd := &cobra.Command{ Use: "validate", @@ -300,7 +304,7 @@ func newCmdConfigValidate(out io.Writer) *cobra.Command { return err } - if err := configutil.ValidateConfig(cfgBytes); err != nil { + if err := configutil.ValidateConfig(cfgBytes, allowExperimental); err != nil { return err } fmt.Fprintln(out, "ok") @@ -310,6 +314,7 @@ func newCmdConfigValidate(out io.Writer) *cobra.Command { Args: cobra.NoArgs, } options.AddConfigFlag(cmd.Flags(), &cfgPath) + cmd.Flags().BoolVar(&allowExperimental, options.AllowExperimentalAPI, false, "Allow validation of experimental, unreleased APIs.") return cmd } diff --git a/cmd/kubeadm/app/cmd/options/constant.go b/cmd/kubeadm/app/cmd/options/constant.go index 35b0e224c798..dca246d9a223 100644 --- a/cmd/kubeadm/app/cmd/options/constant.go +++ b/cmd/kubeadm/app/cmd/options/constant.go @@ -142,4 +142,7 @@ const ( // CleanupTmpDir flag indicates whether reset will cleanup the tmp dir CleanupTmpDir = "cleanup-tmp-dir" + + // AllowExperimentalAPI flag can be used to allow experimental / work in progress APIs + AllowExperimentalAPI = "allow-experimental-api" ) diff --git a/cmd/kubeadm/app/util/config/common.go b/cmd/kubeadm/app/util/config/common.go index 11e54ca63791..3ecc5685ae17 100644 --- a/cmd/kubeadm/app/util/config/common.go +++ b/cmd/kubeadm/app/util/config/common.go @@ -37,6 +37,7 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3" + "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs" "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" @@ -54,7 +55,7 @@ func MarshalKubeadmConfigObject(obj runtime.Object) ([]byte, error) { // validateSupportedVersion checks if the supplied GroupVersion is not on the lists of old unsupported or deprecated GVs. // If it is, an error is returned. -func validateSupportedVersion(gv schema.GroupVersion, allowDeprecated bool) error { +func validateSupportedVersion(gv schema.GroupVersion, allowDeprecated, allowExperimental bool) 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 @@ -72,6 +73,13 @@ func validateSupportedVersion(gv schema.GroupVersion, allowDeprecated bool) erro "kubeadm.k8s.io/v1beta2": "v1.22", } + // v1.28: v1beta4 is released as experimental + experimentalAPIVersions := map[string]string{ + // TODO: https://github.com/kubernetes/kubeadm/issues/2890 + // remove this from experimental once v1beta4 is released + "kubeadm.k8s.io/v1beta4": "v1.28", + } + // Deprecated API versions are supported by us, but can only be used for migration. deprecatedAPIVersions := map[string]struct{}{} @@ -85,6 +93,10 @@ func validateSupportedVersion(gv schema.GroupVersion, allowDeprecated bool) erro klog.Warningf("your configuration file uses a deprecated API spec: %q. Please use 'kubeadm config migrate --old-config old.yaml --new-config new.yaml', which will write the new, similar spec using a newer API version.", gv) } + if _, present := experimentalAPIVersions[gvString]; present && !allowExperimental { + return errors.Errorf("experimental API spec: %q is not allowed. You can use the --%s flag if the command supports it.", gv, options.AllowExperimentalAPI) + } + return nil } @@ -205,7 +217,7 @@ func validateKnownGVKs(gvks []schema.GroupVersionKind) error { // Skip legacy known GVs so that they don't return errors. // This makes the function return errors only for GVs that where never known. - if err := validateSupportedVersion(gvk.GroupVersion(), true); err != nil { + if err := validateSupportedVersion(gvk.GroupVersion(), true, true); err != nil { continue } @@ -229,7 +241,7 @@ func validateKnownGVKs(gvks []schema.GroupVersionKind) error { // MigrateOldConfig migrates an old configuration from a byte slice into a new one (returned again as a byte slice). // Only kubeadm kinds are migrated. -func MigrateOldConfig(oldConfig []byte) ([]byte, error) { +func MigrateOldConfig(oldConfig []byte, allowExperimental bool) ([]byte, error) { newConfig := [][]byte{} gvkmap, err := kubeadmutil.SplitYAMLDocuments(oldConfig) @@ -248,7 +260,7 @@ func MigrateOldConfig(oldConfig []byte) ([]byte, error) { // Migrate InitConfiguration and ClusterConfiguration if there are any in the config if kubeadmutil.GroupVersionKindsHasInitConfiguration(gvks...) || kubeadmutil.GroupVersionKindsHasClusterConfiguration(gvks...) { - o, err := documentMapToInitConfiguration(gvkmap, true, true) + o, err := documentMapToInitConfiguration(gvkmap, true, allowExperimental, true) if err != nil { return []byte{}, err } @@ -261,7 +273,7 @@ func MigrateOldConfig(oldConfig []byte) ([]byte, error) { // Migrate JoinConfiguration if there is any if kubeadmutil.GroupVersionKindsHasJoinConfiguration(gvks...) { - o, err := documentMapToJoinConfiguration(gvkmap, true, true) + o, err := documentMapToJoinConfiguration(gvkmap, true, allowExperimental, true) if err != nil { return []byte{}, err } @@ -277,7 +289,7 @@ func MigrateOldConfig(oldConfig []byte) ([]byte, error) { // ValidateConfig takes a byte slice containing a kubeadm configuration and performs conversion // to internal types and validation. -func ValidateConfig(oldConfig []byte) error { +func ValidateConfig(oldConfig []byte, allowExperimental bool) error { gvkmap, err := kubeadmutil.SplitYAMLDocuments(oldConfig) if err != nil { return err @@ -294,14 +306,14 @@ func ValidateConfig(oldConfig []byte) error { // Validate InitConfiguration and ClusterConfiguration if there are any in the config if kubeadmutil.GroupVersionKindsHasInitConfiguration(gvks...) || kubeadmutil.GroupVersionKindsHasClusterConfiguration(gvks...) { - if _, err := documentMapToInitConfiguration(gvkmap, true, true); err != nil { + if _, err := documentMapToInitConfiguration(gvkmap, true, allowExperimental, true); err != nil { return err } } // Validate JoinConfiguration if there is any if kubeadmutil.GroupVersionKindsHasJoinConfiguration(gvks...) { - if _, err := documentMapToJoinConfiguration(gvkmap, true, true); err != nil { + if _, err := documentMapToJoinConfiguration(gvkmap, true, allowExperimental, true); err != nil { return err } } diff --git a/cmd/kubeadm/app/util/config/common_test.go b/cmd/kubeadm/app/util/config/common_test.go index 1b56386f175c..c713984b22a3 100644 --- a/cmd/kubeadm/app/util/config/common_test.go +++ b/cmd/kubeadm/app/util/config/common_test.go @@ -34,9 +34,10 @@ const KubeadmGroupName = "kubeadm.k8s.io" func TestValidateSupportedVersion(t *testing.T) { tests := []struct { - gv schema.GroupVersion - allowDeprecated bool - expectedErr bool + gv schema.GroupVersion + allowDeprecated bool + allowExperimental bool + expectedErr bool }{ { gv: schema.GroupVersion{ @@ -85,11 +86,25 @@ func TestValidateSupportedVersion(t *testing.T) { Version: "v1", }, }, + { + gv: schema.GroupVersion{ + Group: KubeadmGroupName, + Version: "v1beta4", + }, + allowExperimental: true, + }, + { + gv: schema.GroupVersion{ + Group: KubeadmGroupName, + Version: "v1beta4", + }, + expectedErr: true, + }, } for _, rt := range tests { t.Run(fmt.Sprintf("%s/allowDeprecated:%t", rt.gv, rt.allowDeprecated), func(t *testing.T) { - err := validateSupportedVersion(rt.gv, rt.allowDeprecated) + err := validateSupportedVersion(rt.gv, rt.allowDeprecated, rt.allowExperimental) if rt.expectedErr && err == nil { t.Error("unexpected success") } else if !rt.expectedErr && err != nil { diff --git a/cmd/kubeadm/app/util/config/initconfiguration.go b/cmd/kubeadm/app/util/config/initconfiguration.go index ab1312ab7f3e..407670426bd6 100644 --- a/cmd/kubeadm/app/util/config/initconfiguration.go +++ b/cmd/kubeadm/app/util/config/initconfiguration.go @@ -287,17 +287,17 @@ func BytesToInitConfiguration(b []byte) (*kubeadmapi.InitConfiguration, error) { return nil, err } - return documentMapToInitConfiguration(gvkmap, false, false) + return documentMapToInitConfiguration(gvkmap, false, false, false) } // documentMapToInitConfiguration converts a map of GVKs and YAML documents to defaulted and validated configuration object. -func documentMapToInitConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecated, strictErrors bool) (*kubeadmapi.InitConfiguration, error) { +func documentMapToInitConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecated, allowExperimental, strictErrors bool) (*kubeadmapi.InitConfiguration, error) { var initcfg *kubeadmapi.InitConfiguration var clustercfg *kubeadmapi.ClusterConfiguration for gvk, fileContent := range gvkmap { // first, check if this GVK is supported and possibly not deprecated - if err := validateSupportedVersion(gvk.GroupVersion(), allowDeprecated); err != nil { + if err := validateSupportedVersion(gvk.GroupVersion(), allowDeprecated, allowExperimental); err != nil { return nil, err } diff --git a/cmd/kubeadm/app/util/config/joinconfiguration.go b/cmd/kubeadm/app/util/config/joinconfiguration.go index 0b6e420254bb..1ef041c76168 100644 --- a/cmd/kubeadm/app/util/config/joinconfiguration.go +++ b/cmd/kubeadm/app/util/config/joinconfiguration.go @@ -85,12 +85,12 @@ func LoadJoinConfigurationFromFile(cfgPath string) (*kubeadmapi.JoinConfiguratio return nil, err } - return documentMapToJoinConfiguration(gvkmap, false, false) + return documentMapToJoinConfiguration(gvkmap, false, false, false) } // documentMapToJoinConfiguration takes a map between GVKs and YAML documents (as returned by SplitYAMLDocuments), // finds a JoinConfiguration, decodes it, dynamically defaults it and then validates it prior to return. -func documentMapToJoinConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecated, strictErrors bool) (*kubeadmapi.JoinConfiguration, error) { +func documentMapToJoinConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecated, allowExperimental, strictErrors bool) (*kubeadmapi.JoinConfiguration, error) { joinBytes := []byte{} for gvk, bytes := range gvkmap { // not interested in anything other than JoinConfiguration @@ -99,7 +99,7 @@ func documentMapToJoinConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecat } // check if this version is supported and possibly not deprecated - if err := validateSupportedVersion(gvk.GroupVersion(), allowDeprecated); err != nil { + if err := validateSupportedVersion(gvk.GroupVersion(), allowDeprecated, allowExperimental); err != nil { return nil, err }