diff --git a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go index b80ac470c08..9f65f2de030 100644 --- a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go +++ b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go @@ -41,6 +41,7 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { fuzzJoinConfiguration, fuzzJoinControlPlane, fuzzResetConfiguration, + fuzzUpgradeConfiguration, } } @@ -152,3 +153,13 @@ func fuzzResetConfiguration(obj *kubeadm.ResetConfiguration, c fuzz.Continue) { obj.CertificatesDir = "/tmp" kubeadm.SetDefaultTimeouts(&obj.Timeouts) } + +func fuzzUpgradeConfiguration(obj *kubeadm.UpgradeConfiguration, c fuzz.Continue) { + c.FuzzNoCustom(obj) + + // Pinning values for fields that get defaults if fuzz value is empty string or nil (thus making the round trip test fail) + obj.Node.EtcdUpgrade = ptr.To(true) + obj.Apply.EtcdUpgrade = ptr.To(true) + obj.Apply.CertificateRenewal = ptr.To(false) + obj.Node.CertificateRenewal = ptr.To(false) +} diff --git a/cmd/kubeadm/app/apis/kubeadm/register.go b/cmd/kubeadm/app/apis/kubeadm/register.go index 4b7b831ebf4..e16149965a4 100644 --- a/cmd/kubeadm/app/apis/kubeadm/register.go +++ b/cmd/kubeadm/app/apis/kubeadm/register.go @@ -40,6 +40,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { &ClusterConfiguration{}, &JoinConfiguration{}, &ResetConfiguration{}, + &UpgradeConfiguration{}, ) return nil } diff --git a/cmd/kubeadm/app/apis/kubeadm/types.go b/cmd/kubeadm/app/apis/kubeadm/types.go index 52f6b6c6cf0..ed2e50e9ff0 100644 --- a/cmd/kubeadm/app/apis/kubeadm/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/types.go @@ -39,7 +39,7 @@ type InitConfiguration struct { // the `json:"-"` tag in the external variant of these API types. ClusterConfiguration `json:"-"` - // BootstrapTokens is respected at `kubeadm init` time and describes a set of Bootstrap Tokens to create. + // BootstrapTokens is respected at "kubeadm init" time and describes a set of Bootstrap Tokens to create. BootstrapTokens []bootstraptokenv1.BootstrapToken // DryRun tells if the dry run mode is enabled, don't apply any change if it is and just output what would be done. @@ -221,7 +221,7 @@ type APIEndpoint struct { // NodeRegistrationOptions holds fields that relate to registering a new control-plane or node to the cluster, either via "kubeadm init" or "kubeadm join" type NodeRegistrationOptions struct { - // Name is the `.Metadata.Name` field of the Node API object that will be created in this `kubeadm init` or `kubeadm join` operation. + // Name is the `.Metadata.Name` field of the Node API object that will be created in this "kubeadm init" or "kubeadm join" operation. // This field is also used in the CommonName field of the kubelet's client certificate to the API server. // Defaults to the hostname of the node if not provided. Name string @@ -541,6 +541,125 @@ type ResetConfiguration struct { Timeouts *Timeouts } +// UpgradeApplyConfiguration contains a list of configurable options which are specific to the "kubeadm upgrade apply" command. +type UpgradeApplyConfiguration struct { + // KubernetesVersion is the target version of the control plane. + KubernetesVersion string + + // AllowExperimentalUpgrades instructs kubeadm to show unstable versions of Kubernetes as an upgrade + // alternative and allows upgrading to an alpha/beta/release candidate version of Kubernetes. + // Default: false + AllowExperimentalUpgrades *bool + + // Enable AllowRCUpgrades will show release candidate versions of Kubernetes as an upgrade alternative and + // allows upgrading to a release candidate version of Kubernetes. + AllowRCUpgrades *bool + + // CertificateRenewal instructs kubeadm to execute certificate renewal during upgrades. + CertificateRenewal *bool + + // DryRun tells if the dry run mode is enabled, don't apply any change if it is and just output what would be done. + DryRun *bool + + // EtcdUpgrade instructs kubeadm to execute etcd upgrade during upgrades. + EtcdUpgrade *bool + + // ForceUpgrade flag instructs kubeadm to upgrade the cluster without prompting for confirmation. + ForceUpgrade *bool + + // IgnorePreflightErrors provides a slice of pre-flight errors to be ignored during the upgrade process, e.g. 'IsPrivilegedUser,Swap'. + // Value 'all' ignores errors from all checks. + IgnorePreflightErrors []string + + // Patches contains options related to applying patches to components deployed by kubeadm during "kubeadm upgrade". + Patches *Patches + + // PrintConfig specifies whether the configuration file that will be used in the upgrade should be printed or not. + PrintConfig *bool + + // SkipPhases is a list of phases to skip during command execution. + // NOTE: This field is currently ignored for "kubeadm upgrade apply", but in the future it will be supported. + SkipPhases []string +} + +// UpgradeDiffConfiguration contains a list of configurable options which are specific to the "kubeadm upgrade diff" command. +type UpgradeDiffConfiguration struct { + // KubernetesVersion is the target version of the control plane. + KubernetesVersion string + + // DiffContextLines is the number of lines of context in the diff. + DiffContextLines int +} + +// UpgradeNodeConfiguration contains a list of configurable options which are specific to the "kubeadm upgrade node" command. +type UpgradeNodeConfiguration struct { + // CertificateRenewal instructs kubeadm to execute certificate renewal during upgrades. + CertificateRenewal *bool + + // DryRun tells if the dry run mode is enabled, don't apply any change if it is and just output what would be done. + DryRun *bool + + // EtcdUpgrade instructs kubeadm to execute etcd upgrade during upgrades. + EtcdUpgrade *bool + + // IgnorePreflightErrors provides a slice of pre-flight errors to be ignored during the upgrade process, e.g. 'IsPrivilegedUser,Swap'. + // Value 'all' ignores errors from all checks. + IgnorePreflightErrors []string + + // SkipPhases is a list of phases to skip during command execution. + // The list of phases can be obtained with the "kubeadm upgrade node phase --help" command. + SkipPhases []string + + // Patches contains options related to applying patches to components deployed by kubeadm during "kubeadm upgrade". + Patches *Patches +} + +// UpgradePlanConfiguration contains a list of configurable options which are specific to the "kubeadm upgrade plan" command. +type UpgradePlanConfiguration struct { + // KubernetesVersion is the target version of the control plane. + // +optional + KubernetesVersion string + + // AllowExperimentalUpgrades instructs kubeadm to show unstable versions of Kubernetes as an upgrade + // alternative and allows upgrading to an alpha/beta/release candidate version of Kubernetes. + // Default: false + // +optional + AllowExperimentalUpgrades *bool + + // Enable AllowRCUpgrades will show release candidate versions of Kubernetes as an upgrade alternative and + // allows upgrading to a release candidate version of Kubernetes. + AllowRCUpgrades *bool + + // DryRun tells if the dry run mode is enabled, don't apply any change if it is and just output what would be done. + DryRun *bool + + // IgnorePreflightErrors provides a slice of pre-flight errors to be ignored during the upgrade process, e.g. 'IsPrivilegedUser,Swap'. + // Value 'all' ignores errors from all checks. + IgnorePreflightErrors []string + + // PrintConfig specifies whether the configuration file that will be used in the upgrade should be printed or not. + PrintConfig *bool +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// UpgradeConfiguration contains a list of options that are specific to "kubeadm upgrade" subcommands. +type UpgradeConfiguration struct { + metav1.TypeMeta + + // Apply holds a list of options that are specific to the "kubeadm upgrade apply" command. + Apply UpgradeApplyConfiguration + + // Diff holds a list of options that are specific to the "kubeadm upgrade diff" command. + Diff UpgradeDiffConfiguration + + // Node holds a list of options that are specific to the "kubeadm upgrade node" command. + Node UpgradeNodeConfiguration + + // Plan holds a list of options that are specific to the "kubeadm upgrade plan" command. + Plan UpgradePlanConfiguration +} + const ( // UnmountFlagMNTForce represents the flag "MNT_FORCE" UnmountFlagMNTForce = "MNT_FORCE" diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta4/defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1beta4/defaults.go index 9a9f6ce3857..841b8e9ff45 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta4/defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta4/defaults.go @@ -256,3 +256,22 @@ func SetDefaults_Timeouts(obj *Timeouts) { } } } + +// SetDefaults_UpgradeConfiguration assigns default values for the UpgradeConfiguration +func SetDefaults_UpgradeConfiguration(obj *UpgradeConfiguration) { + if obj.Node.EtcdUpgrade == nil { + obj.Node.EtcdUpgrade = ptr.To(true) + } + + if obj.Node.CertificateRenewal == nil { + obj.Node.CertificateRenewal = ptr.To(true) + } + + if obj.Apply.EtcdUpgrade == nil { + obj.Apply.EtcdUpgrade = ptr.To(true) + } + + if obj.Apply.CertificateRenewal == nil { + obj.Apply.CertificateRenewal = ptr.To(true) + } +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta4/doc.go b/cmd/kubeadm/app/apis/kubeadm/v1beta4/doc.go index f3cb6831260..f19b254fea2 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta4/doc.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta4/doc.go @@ -41,6 +41,9 @@ limitations under the License. // that can be used to configure various timeouts. // - Add the `NodeRegistration.ImagePullSerial` field in 'InitConfiguration` and `JoinConfiguration`, which // can be used to control if kubeadm pulls images serially or in parallel. +// - The UpgradeConfiguration kubeadm API is now supported in v1beta4 when passing --config to "kubeadm upgrade" subcommands. +// Usage of component configuration for kubelet and kube-proxy, InitConfiguration and ClusterConfiguration is deprecated +// and will be ignored when passing --config to upgrade subcommands. // // Migration from old kubeadm config versions // diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta4/register.go b/cmd/kubeadm/app/apis/kubeadm/v1beta4/register.go index fd820962307..fd0e5566f4d 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta4/register.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta4/register.go @@ -49,6 +49,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { &ClusterConfiguration{}, &JoinConfiguration{}, &ResetConfiguration{}, + &UpgradeConfiguration{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta4/types.go b/cmd/kubeadm/app/apis/kubeadm/v1beta4/types.go index 62c76f3cc4d..b3af02270ef 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta4/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta4/types.go @@ -597,3 +597,151 @@ type Timeouts struct { // +optional Discovery *metav1.Duration `json:"discovery,omitempty"` } + +// UpgradeApplyConfiguration contains a list of configurable options which are specific to the "kubeadm upgrade apply" command. +type UpgradeApplyConfiguration struct { + // KubernetesVersion is the target version of the control plane. + // +optional + KubernetesVersion string `json:"kubernetesVersion,omitempty"` + + // AllowExperimentalUpgrades instructs kubeadm to show unstable versions of Kubernetes as an upgrade + // alternative and allows upgrading to an alpha/beta/release candidate version of Kubernetes. + // Default: false + // +optional + AllowExperimentalUpgrades *bool `json:"allowExperimentalUpgrades,omitempty"` + + // Enable AllowRCUpgrades will show release candidate versions of Kubernetes as an upgrade alternative and + // allows upgrading to a release candidate version of Kubernetes. + // +optional + AllowRCUpgrades *bool `json:"allowRCUpgrades,omitempty"` + + // CertificateRenewal instructs kubeadm to execute certificate renewal during upgrades. + // Defaults to true. + // +optional + CertificateRenewal *bool `json:"certificateRenewal,omitempty"` + + // DryRun tells if the dry run mode is enabled, don't apply any change if it is and just output what would be done. + // +optional + DryRun *bool `json:"dryRun,omitempty"` + + // EtcdUpgrade instructs kubeadm to execute etcd upgrade during upgrades. + // Defaults to true. + // +optional + EtcdUpgrade *bool `json:"etcdUpgrade,omitempty"` + + // ForceUpgrade flag instructs kubeadm to upgrade the cluster without prompting for confirmation. + // +optional + ForceUpgrade *bool `json:"forceUpgrade,omitempty"` + + // IgnorePreflightErrors provides a slice of pre-flight errors to be ignored during the upgrade process, e.g. 'IsPrivilegedUser,Swap'. + // Value 'all' ignores errors from all checks. + // +optional + IgnorePreflightErrors []string `json:"ignorePreflightErrors,omitempty"` + + // Patches contains options related to applying patches to components deployed by kubeadm during "kubeadm upgrade". + // +optional + Patches *Patches `json:"patches,omitempty"` + + // PrintConfig specifies whether the configuration file that will be used in the upgrade should be printed or not. + // +optional + PrintConfig *bool `json:"printConfig,omitempty"` + + // SkipPhases is a list of phases to skip during command execution. + // NOTE: This field is currently ignored for "kubeadm upgrade apply", but in the future it will be supported. + SkipPhases []string +} + +// UpgradeDiffConfiguration contains a list of configurable options which are specific to the "kubeadm upgrade diff" command. +type UpgradeDiffConfiguration struct { + // KubernetesVersion is the target version of the control plane. + // +optional + KubernetesVersion string `json:"kubernetesVersion,omitempty"` + + // DiffContextLines is the number of lines of context in the diff. + // +optional + DiffContextLines int `json:"contextLines,omitempty"` +} + +// UpgradeNodeConfiguration contains a list of configurable options which are specific to the "kubeadm upgrade node" command. +type UpgradeNodeConfiguration struct { + // CertificateRenewal instructs kubeadm to execute certificate renewal during upgrades. + // Defaults to true. + // +optional + CertificateRenewal *bool `json:"certificateRenewal,omitempty"` + + // DryRun tells if the dry run mode is enabled, don't apply any change if it is and just output what would be done. + // +optional + DryRun *bool `json:"dryRun,omitempty"` + + // EtcdUpgrade instructs kubeadm to execute etcd upgrade during upgrades. + // Defaults to true. + // +optional + EtcdUpgrade *bool `json:"etcdUpgrade,omitempty"` + + // IgnorePreflightErrors provides a slice of pre-flight errors to be ignored during the upgrade process, e.g. 'IsPrivilegedUser,Swap'. + // Value 'all' ignores errors from all checks. + // +optional + IgnorePreflightErrors []string `json:"ignorePreflightErrors,omitempty"` + + // SkipPhases is a list of phases to skip during command execution. + // The list of phases can be obtained with the "kubeadm upgrade node phase --help" command. + // +optional + SkipPhases []string `json:"skipPhases,omitempty"` + + // Patches contains options related to applying patches to components deployed by kubeadm during "kubeadm upgrade". + // +optional + Patches *Patches `json:"patches,omitempty"` +} + +// UpgradePlanConfiguration contains a list of configurable options which are specific to the "kubeadm upgrade plan" command. +type UpgradePlanConfiguration struct { + // KubernetesVersion is the target version of the control plane. + KubernetesVersion string `json:"kubernetesVersion,omitempty"` + + // AllowExperimentalUpgrades instructs kubeadm to show unstable versions of Kubernetes as an upgrade + // alternative and allows upgrading to an alpha/beta/release candidate version of Kubernetes. + // Default: false + // +optional + AllowExperimentalUpgrades *bool `json:"allowExperimentalUpgrades,omitempty"` + + // Enable AllowRCUpgrades will show release candidate versions of Kubernetes as an upgrade alternative and + // allows upgrading to a release candidate version of Kubernetes. + // +optional + AllowRCUpgrades *bool `json:"allowRCUpgrades,omitempty"` + + // DryRun tells if the dry run mode is enabled, don't apply any change if it is and just output what would be done. + // +optional + DryRun *bool `json:"dryRun,omitempty"` + + // IgnorePreflightErrors provides a slice of pre-flight errors to be ignored during the upgrade process, e.g. 'IsPrivilegedUser,Swap'. + // Value 'all' ignores errors from all checks. + // +optional + IgnorePreflightErrors []string `json:"ignorePreflightErrors,omitempty"` + + // PrintConfig specifies whether the configuration file that will be used in the upgrade should be printed or not. + // +optional + PrintConfig *bool `json:"printConfig,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// UpgradeConfiguration contains a list of options that are specific to "kubeadm upgrade" subcommands. +type UpgradeConfiguration struct { + metav1.TypeMeta `json:",inline"` + + // Apply holds a list of options that are specific to the "kubeadm upgrade apply" command. + // +optional + Apply UpgradeApplyConfiguration `json:"apply,omitempty"` + + // Diff holds a list of options that are specific to the "kubeadm upgrade diff" command. + // +optional + Diff UpgradeDiffConfiguration `json:"diff,omitempty"` + + // Node holds a list of options that are specific to the "kubeadm upgrade node" command. + // +optional + Node UpgradeNodeConfiguration `json:"node,omitempty"` + + // Plan holds a list of options that are specific to the "kubeadm upgrade plan" command. + // +optional + Plan UpgradePlanConfiguration `json:"plan,omitempty"` +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta4/zz_generated.conversion.go b/cmd/kubeadm/app/apis/kubeadm/v1beta4/zz_generated.conversion.go index 4cff41d0938..d29d019b79d 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta4/zz_generated.conversion.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta4/zz_generated.conversion.go @@ -244,6 +244,56 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*UpgradeApplyConfiguration)(nil), (*kubeadm.UpgradeApplyConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta4_UpgradeApplyConfiguration_To_kubeadm_UpgradeApplyConfiguration(a.(*UpgradeApplyConfiguration), b.(*kubeadm.UpgradeApplyConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*kubeadm.UpgradeApplyConfiguration)(nil), (*UpgradeApplyConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kubeadm_UpgradeApplyConfiguration_To_v1beta4_UpgradeApplyConfiguration(a.(*kubeadm.UpgradeApplyConfiguration), b.(*UpgradeApplyConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*UpgradeConfiguration)(nil), (*kubeadm.UpgradeConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta4_UpgradeConfiguration_To_kubeadm_UpgradeConfiguration(a.(*UpgradeConfiguration), b.(*kubeadm.UpgradeConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*kubeadm.UpgradeConfiguration)(nil), (*UpgradeConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kubeadm_UpgradeConfiguration_To_v1beta4_UpgradeConfiguration(a.(*kubeadm.UpgradeConfiguration), b.(*UpgradeConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*UpgradeDiffConfiguration)(nil), (*kubeadm.UpgradeDiffConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta4_UpgradeDiffConfiguration_To_kubeadm_UpgradeDiffConfiguration(a.(*UpgradeDiffConfiguration), b.(*kubeadm.UpgradeDiffConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*kubeadm.UpgradeDiffConfiguration)(nil), (*UpgradeDiffConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kubeadm_UpgradeDiffConfiguration_To_v1beta4_UpgradeDiffConfiguration(a.(*kubeadm.UpgradeDiffConfiguration), b.(*UpgradeDiffConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*UpgradeNodeConfiguration)(nil), (*kubeadm.UpgradeNodeConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta4_UpgradeNodeConfiguration_To_kubeadm_UpgradeNodeConfiguration(a.(*UpgradeNodeConfiguration), b.(*kubeadm.UpgradeNodeConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*kubeadm.UpgradeNodeConfiguration)(nil), (*UpgradeNodeConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kubeadm_UpgradeNodeConfiguration_To_v1beta4_UpgradeNodeConfiguration(a.(*kubeadm.UpgradeNodeConfiguration), b.(*UpgradeNodeConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*UpgradePlanConfiguration)(nil), (*kubeadm.UpgradePlanConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta4_UpgradePlanConfiguration_To_kubeadm_UpgradePlanConfiguration(a.(*UpgradePlanConfiguration), b.(*kubeadm.UpgradePlanConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*kubeadm.UpgradePlanConfiguration)(nil), (*UpgradePlanConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kubeadm_UpgradePlanConfiguration_To_v1beta4_UpgradePlanConfiguration(a.(*kubeadm.UpgradePlanConfiguration), b.(*UpgradePlanConfiguration), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*kubeadm.APIServer)(nil), (*APIServer)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_kubeadm_APIServer_To_v1beta4_APIServer(a.(*kubeadm.APIServer), b.(*APIServer), scope) }); err != nil { @@ -940,3 +990,167 @@ func autoConvert_kubeadm_Timeouts_To_v1beta4_Timeouts(in *kubeadm.Timeouts, out func Convert_kubeadm_Timeouts_To_v1beta4_Timeouts(in *kubeadm.Timeouts, out *Timeouts, s conversion.Scope) error { return autoConvert_kubeadm_Timeouts_To_v1beta4_Timeouts(in, out, s) } + +func autoConvert_v1beta4_UpgradeApplyConfiguration_To_kubeadm_UpgradeApplyConfiguration(in *UpgradeApplyConfiguration, out *kubeadm.UpgradeApplyConfiguration, s conversion.Scope) error { + out.KubernetesVersion = in.KubernetesVersion + out.AllowExperimentalUpgrades = (*bool)(unsafe.Pointer(in.AllowExperimentalUpgrades)) + out.AllowRCUpgrades = (*bool)(unsafe.Pointer(in.AllowRCUpgrades)) + out.CertificateRenewal = (*bool)(unsafe.Pointer(in.CertificateRenewal)) + out.DryRun = (*bool)(unsafe.Pointer(in.DryRun)) + out.EtcdUpgrade = (*bool)(unsafe.Pointer(in.EtcdUpgrade)) + out.ForceUpgrade = (*bool)(unsafe.Pointer(in.ForceUpgrade)) + out.IgnorePreflightErrors = *(*[]string)(unsafe.Pointer(&in.IgnorePreflightErrors)) + out.Patches = (*kubeadm.Patches)(unsafe.Pointer(in.Patches)) + out.PrintConfig = (*bool)(unsafe.Pointer(in.PrintConfig)) + out.SkipPhases = *(*[]string)(unsafe.Pointer(&in.SkipPhases)) + return nil +} + +// Convert_v1beta4_UpgradeApplyConfiguration_To_kubeadm_UpgradeApplyConfiguration is an autogenerated conversion function. +func Convert_v1beta4_UpgradeApplyConfiguration_To_kubeadm_UpgradeApplyConfiguration(in *UpgradeApplyConfiguration, out *kubeadm.UpgradeApplyConfiguration, s conversion.Scope) error { + return autoConvert_v1beta4_UpgradeApplyConfiguration_To_kubeadm_UpgradeApplyConfiguration(in, out, s) +} + +func autoConvert_kubeadm_UpgradeApplyConfiguration_To_v1beta4_UpgradeApplyConfiguration(in *kubeadm.UpgradeApplyConfiguration, out *UpgradeApplyConfiguration, s conversion.Scope) error { + out.KubernetesVersion = in.KubernetesVersion + out.AllowExperimentalUpgrades = (*bool)(unsafe.Pointer(in.AllowExperimentalUpgrades)) + out.AllowRCUpgrades = (*bool)(unsafe.Pointer(in.AllowRCUpgrades)) + out.CertificateRenewal = (*bool)(unsafe.Pointer(in.CertificateRenewal)) + out.DryRun = (*bool)(unsafe.Pointer(in.DryRun)) + out.EtcdUpgrade = (*bool)(unsafe.Pointer(in.EtcdUpgrade)) + out.ForceUpgrade = (*bool)(unsafe.Pointer(in.ForceUpgrade)) + out.IgnorePreflightErrors = *(*[]string)(unsafe.Pointer(&in.IgnorePreflightErrors)) + out.Patches = (*Patches)(unsafe.Pointer(in.Patches)) + out.PrintConfig = (*bool)(unsafe.Pointer(in.PrintConfig)) + out.SkipPhases = *(*[]string)(unsafe.Pointer(&in.SkipPhases)) + return nil +} + +// Convert_kubeadm_UpgradeApplyConfiguration_To_v1beta4_UpgradeApplyConfiguration is an autogenerated conversion function. +func Convert_kubeadm_UpgradeApplyConfiguration_To_v1beta4_UpgradeApplyConfiguration(in *kubeadm.UpgradeApplyConfiguration, out *UpgradeApplyConfiguration, s conversion.Scope) error { + return autoConvert_kubeadm_UpgradeApplyConfiguration_To_v1beta4_UpgradeApplyConfiguration(in, out, s) +} + +func autoConvert_v1beta4_UpgradeConfiguration_To_kubeadm_UpgradeConfiguration(in *UpgradeConfiguration, out *kubeadm.UpgradeConfiguration, s conversion.Scope) error { + if err := Convert_v1beta4_UpgradeApplyConfiguration_To_kubeadm_UpgradeApplyConfiguration(&in.Apply, &out.Apply, s); err != nil { + return err + } + if err := Convert_v1beta4_UpgradeDiffConfiguration_To_kubeadm_UpgradeDiffConfiguration(&in.Diff, &out.Diff, s); err != nil { + return err + } + if err := Convert_v1beta4_UpgradeNodeConfiguration_To_kubeadm_UpgradeNodeConfiguration(&in.Node, &out.Node, s); err != nil { + return err + } + if err := Convert_v1beta4_UpgradePlanConfiguration_To_kubeadm_UpgradePlanConfiguration(&in.Plan, &out.Plan, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta4_UpgradeConfiguration_To_kubeadm_UpgradeConfiguration is an autogenerated conversion function. +func Convert_v1beta4_UpgradeConfiguration_To_kubeadm_UpgradeConfiguration(in *UpgradeConfiguration, out *kubeadm.UpgradeConfiguration, s conversion.Scope) error { + return autoConvert_v1beta4_UpgradeConfiguration_To_kubeadm_UpgradeConfiguration(in, out, s) +} + +func autoConvert_kubeadm_UpgradeConfiguration_To_v1beta4_UpgradeConfiguration(in *kubeadm.UpgradeConfiguration, out *UpgradeConfiguration, s conversion.Scope) error { + if err := Convert_kubeadm_UpgradeApplyConfiguration_To_v1beta4_UpgradeApplyConfiguration(&in.Apply, &out.Apply, s); err != nil { + return err + } + if err := Convert_kubeadm_UpgradeDiffConfiguration_To_v1beta4_UpgradeDiffConfiguration(&in.Diff, &out.Diff, s); err != nil { + return err + } + if err := Convert_kubeadm_UpgradeNodeConfiguration_To_v1beta4_UpgradeNodeConfiguration(&in.Node, &out.Node, s); err != nil { + return err + } + if err := Convert_kubeadm_UpgradePlanConfiguration_To_v1beta4_UpgradePlanConfiguration(&in.Plan, &out.Plan, s); err != nil { + return err + } + return nil +} + +// Convert_kubeadm_UpgradeConfiguration_To_v1beta4_UpgradeConfiguration is an autogenerated conversion function. +func Convert_kubeadm_UpgradeConfiguration_To_v1beta4_UpgradeConfiguration(in *kubeadm.UpgradeConfiguration, out *UpgradeConfiguration, s conversion.Scope) error { + return autoConvert_kubeadm_UpgradeConfiguration_To_v1beta4_UpgradeConfiguration(in, out, s) +} + +func autoConvert_v1beta4_UpgradeDiffConfiguration_To_kubeadm_UpgradeDiffConfiguration(in *UpgradeDiffConfiguration, out *kubeadm.UpgradeDiffConfiguration, s conversion.Scope) error { + out.KubernetesVersion = in.KubernetesVersion + out.DiffContextLines = in.DiffContextLines + return nil +} + +// Convert_v1beta4_UpgradeDiffConfiguration_To_kubeadm_UpgradeDiffConfiguration is an autogenerated conversion function. +func Convert_v1beta4_UpgradeDiffConfiguration_To_kubeadm_UpgradeDiffConfiguration(in *UpgradeDiffConfiguration, out *kubeadm.UpgradeDiffConfiguration, s conversion.Scope) error { + return autoConvert_v1beta4_UpgradeDiffConfiguration_To_kubeadm_UpgradeDiffConfiguration(in, out, s) +} + +func autoConvert_kubeadm_UpgradeDiffConfiguration_To_v1beta4_UpgradeDiffConfiguration(in *kubeadm.UpgradeDiffConfiguration, out *UpgradeDiffConfiguration, s conversion.Scope) error { + out.KubernetesVersion = in.KubernetesVersion + out.DiffContextLines = in.DiffContextLines + return nil +} + +// Convert_kubeadm_UpgradeDiffConfiguration_To_v1beta4_UpgradeDiffConfiguration is an autogenerated conversion function. +func Convert_kubeadm_UpgradeDiffConfiguration_To_v1beta4_UpgradeDiffConfiguration(in *kubeadm.UpgradeDiffConfiguration, out *UpgradeDiffConfiguration, s conversion.Scope) error { + return autoConvert_kubeadm_UpgradeDiffConfiguration_To_v1beta4_UpgradeDiffConfiguration(in, out, s) +} + +func autoConvert_v1beta4_UpgradeNodeConfiguration_To_kubeadm_UpgradeNodeConfiguration(in *UpgradeNodeConfiguration, out *kubeadm.UpgradeNodeConfiguration, s conversion.Scope) error { + out.CertificateRenewal = (*bool)(unsafe.Pointer(in.CertificateRenewal)) + out.DryRun = (*bool)(unsafe.Pointer(in.DryRun)) + out.EtcdUpgrade = (*bool)(unsafe.Pointer(in.EtcdUpgrade)) + out.IgnorePreflightErrors = *(*[]string)(unsafe.Pointer(&in.IgnorePreflightErrors)) + out.SkipPhases = *(*[]string)(unsafe.Pointer(&in.SkipPhases)) + out.Patches = (*kubeadm.Patches)(unsafe.Pointer(in.Patches)) + return nil +} + +// Convert_v1beta4_UpgradeNodeConfiguration_To_kubeadm_UpgradeNodeConfiguration is an autogenerated conversion function. +func Convert_v1beta4_UpgradeNodeConfiguration_To_kubeadm_UpgradeNodeConfiguration(in *UpgradeNodeConfiguration, out *kubeadm.UpgradeNodeConfiguration, s conversion.Scope) error { + return autoConvert_v1beta4_UpgradeNodeConfiguration_To_kubeadm_UpgradeNodeConfiguration(in, out, s) +} + +func autoConvert_kubeadm_UpgradeNodeConfiguration_To_v1beta4_UpgradeNodeConfiguration(in *kubeadm.UpgradeNodeConfiguration, out *UpgradeNodeConfiguration, s conversion.Scope) error { + out.CertificateRenewal = (*bool)(unsafe.Pointer(in.CertificateRenewal)) + out.DryRun = (*bool)(unsafe.Pointer(in.DryRun)) + out.EtcdUpgrade = (*bool)(unsafe.Pointer(in.EtcdUpgrade)) + out.IgnorePreflightErrors = *(*[]string)(unsafe.Pointer(&in.IgnorePreflightErrors)) + out.SkipPhases = *(*[]string)(unsafe.Pointer(&in.SkipPhases)) + out.Patches = (*Patches)(unsafe.Pointer(in.Patches)) + return nil +} + +// Convert_kubeadm_UpgradeNodeConfiguration_To_v1beta4_UpgradeNodeConfiguration is an autogenerated conversion function. +func Convert_kubeadm_UpgradeNodeConfiguration_To_v1beta4_UpgradeNodeConfiguration(in *kubeadm.UpgradeNodeConfiguration, out *UpgradeNodeConfiguration, s conversion.Scope) error { + return autoConvert_kubeadm_UpgradeNodeConfiguration_To_v1beta4_UpgradeNodeConfiguration(in, out, s) +} + +func autoConvert_v1beta4_UpgradePlanConfiguration_To_kubeadm_UpgradePlanConfiguration(in *UpgradePlanConfiguration, out *kubeadm.UpgradePlanConfiguration, s conversion.Scope) error { + out.KubernetesVersion = in.KubernetesVersion + out.AllowExperimentalUpgrades = (*bool)(unsafe.Pointer(in.AllowExperimentalUpgrades)) + out.AllowRCUpgrades = (*bool)(unsafe.Pointer(in.AllowRCUpgrades)) + out.DryRun = (*bool)(unsafe.Pointer(in.DryRun)) + out.IgnorePreflightErrors = *(*[]string)(unsafe.Pointer(&in.IgnorePreflightErrors)) + out.PrintConfig = (*bool)(unsafe.Pointer(in.PrintConfig)) + return nil +} + +// Convert_v1beta4_UpgradePlanConfiguration_To_kubeadm_UpgradePlanConfiguration is an autogenerated conversion function. +func Convert_v1beta4_UpgradePlanConfiguration_To_kubeadm_UpgradePlanConfiguration(in *UpgradePlanConfiguration, out *kubeadm.UpgradePlanConfiguration, s conversion.Scope) error { + return autoConvert_v1beta4_UpgradePlanConfiguration_To_kubeadm_UpgradePlanConfiguration(in, out, s) +} + +func autoConvert_kubeadm_UpgradePlanConfiguration_To_v1beta4_UpgradePlanConfiguration(in *kubeadm.UpgradePlanConfiguration, out *UpgradePlanConfiguration, s conversion.Scope) error { + out.KubernetesVersion = in.KubernetesVersion + out.AllowExperimentalUpgrades = (*bool)(unsafe.Pointer(in.AllowExperimentalUpgrades)) + out.AllowRCUpgrades = (*bool)(unsafe.Pointer(in.AllowRCUpgrades)) + out.DryRun = (*bool)(unsafe.Pointer(in.DryRun)) + out.IgnorePreflightErrors = *(*[]string)(unsafe.Pointer(&in.IgnorePreflightErrors)) + out.PrintConfig = (*bool)(unsafe.Pointer(in.PrintConfig)) + return nil +} + +// Convert_kubeadm_UpgradePlanConfiguration_To_v1beta4_UpgradePlanConfiguration is an autogenerated conversion function. +func Convert_kubeadm_UpgradePlanConfiguration_To_v1beta4_UpgradePlanConfiguration(in *kubeadm.UpgradePlanConfiguration, out *UpgradePlanConfiguration, s conversion.Scope) error { + return autoConvert_kubeadm_UpgradePlanConfiguration_To_v1beta4_UpgradePlanConfiguration(in, out, s) +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta4/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/kubeadm/v1beta4/zz_generated.deepcopy.go index 55ec68abca4..0b6fb54003f 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta4/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta4/zz_generated.deepcopy.go @@ -658,3 +658,201 @@ func (in *Timeouts) DeepCopy() *Timeouts { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpgradeApplyConfiguration) DeepCopyInto(out *UpgradeApplyConfiguration) { + *out = *in + if in.AllowExperimentalUpgrades != nil { + in, out := &in.AllowExperimentalUpgrades, &out.AllowExperimentalUpgrades + *out = new(bool) + **out = **in + } + if in.AllowRCUpgrades != nil { + in, out := &in.AllowRCUpgrades, &out.AllowRCUpgrades + *out = new(bool) + **out = **in + } + if in.CertificateRenewal != nil { + in, out := &in.CertificateRenewal, &out.CertificateRenewal + *out = new(bool) + **out = **in + } + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = new(bool) + **out = **in + } + if in.EtcdUpgrade != nil { + in, out := &in.EtcdUpgrade, &out.EtcdUpgrade + *out = new(bool) + **out = **in + } + if in.ForceUpgrade != nil { + in, out := &in.ForceUpgrade, &out.ForceUpgrade + *out = new(bool) + **out = **in + } + if in.IgnorePreflightErrors != nil { + in, out := &in.IgnorePreflightErrors, &out.IgnorePreflightErrors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Patches != nil { + in, out := &in.Patches, &out.Patches + *out = new(Patches) + **out = **in + } + if in.PrintConfig != nil { + in, out := &in.PrintConfig, &out.PrintConfig + *out = new(bool) + **out = **in + } + if in.SkipPhases != nil { + in, out := &in.SkipPhases, &out.SkipPhases + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradeApplyConfiguration. +func (in *UpgradeApplyConfiguration) DeepCopy() *UpgradeApplyConfiguration { + if in == nil { + return nil + } + out := new(UpgradeApplyConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpgradeConfiguration) DeepCopyInto(out *UpgradeConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.Apply.DeepCopyInto(&out.Apply) + out.Diff = in.Diff + in.Node.DeepCopyInto(&out.Node) + in.Plan.DeepCopyInto(&out.Plan) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradeConfiguration. +func (in *UpgradeConfiguration) DeepCopy() *UpgradeConfiguration { + if in == nil { + return nil + } + out := new(UpgradeConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UpgradeConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpgradeDiffConfiguration) DeepCopyInto(out *UpgradeDiffConfiguration) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradeDiffConfiguration. +func (in *UpgradeDiffConfiguration) DeepCopy() *UpgradeDiffConfiguration { + if in == nil { + return nil + } + out := new(UpgradeDiffConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpgradeNodeConfiguration) DeepCopyInto(out *UpgradeNodeConfiguration) { + *out = *in + if in.CertificateRenewal != nil { + in, out := &in.CertificateRenewal, &out.CertificateRenewal + *out = new(bool) + **out = **in + } + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = new(bool) + **out = **in + } + if in.EtcdUpgrade != nil { + in, out := &in.EtcdUpgrade, &out.EtcdUpgrade + *out = new(bool) + **out = **in + } + if in.IgnorePreflightErrors != nil { + in, out := &in.IgnorePreflightErrors, &out.IgnorePreflightErrors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SkipPhases != nil { + in, out := &in.SkipPhases, &out.SkipPhases + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Patches != nil { + in, out := &in.Patches, &out.Patches + *out = new(Patches) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradeNodeConfiguration. +func (in *UpgradeNodeConfiguration) DeepCopy() *UpgradeNodeConfiguration { + if in == nil { + return nil + } + out := new(UpgradeNodeConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpgradePlanConfiguration) DeepCopyInto(out *UpgradePlanConfiguration) { + *out = *in + if in.AllowExperimentalUpgrades != nil { + in, out := &in.AllowExperimentalUpgrades, &out.AllowExperimentalUpgrades + *out = new(bool) + **out = **in + } + if in.AllowRCUpgrades != nil { + in, out := &in.AllowRCUpgrades, &out.AllowRCUpgrades + *out = new(bool) + **out = **in + } + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = new(bool) + **out = **in + } + if in.IgnorePreflightErrors != nil { + in, out := &in.IgnorePreflightErrors, &out.IgnorePreflightErrors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.PrintConfig != nil { + in, out := &in.PrintConfig, &out.PrintConfig + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradePlanConfiguration. +func (in *UpgradePlanConfiguration) DeepCopy() *UpgradePlanConfiguration { + if in == nil { + return nil + } + out := new(UpgradePlanConfiguration) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta4/zz_generated.defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1beta4/zz_generated.defaults.go index 92a6cb2a7a1..af5fa24462d 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta4/zz_generated.defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta4/zz_generated.defaults.go @@ -33,6 +33,7 @@ func RegisterDefaults(scheme *runtime.Scheme) error { scheme.AddTypeDefaultingFunc(&InitConfiguration{}, func(obj interface{}) { SetObjectDefaults_InitConfiguration(obj.(*InitConfiguration)) }) scheme.AddTypeDefaultingFunc(&JoinConfiguration{}, func(obj interface{}) { SetObjectDefaults_JoinConfiguration(obj.(*JoinConfiguration)) }) scheme.AddTypeDefaultingFunc(&ResetConfiguration{}, func(obj interface{}) { SetObjectDefaults_ResetConfiguration(obj.(*ResetConfiguration)) }) + scheme.AddTypeDefaultingFunc(&UpgradeConfiguration{}, func(obj interface{}) { SetObjectDefaults_UpgradeConfiguration(obj.(*UpgradeConfiguration)) }) return nil } @@ -87,3 +88,7 @@ func SetObjectDefaults_ResetConfiguration(in *ResetConfiguration) { SetDefaults_Timeouts(in.Timeouts) } } + +func SetObjectDefaults_UpgradeConfiguration(in *UpgradeConfiguration) { + SetDefaults_UpgradeConfiguration(in) +} diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go index a6788f8805f..56d2a7e312c 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go @@ -758,3 +758,12 @@ func ValidateImagePullPolicy(policy corev1.PullPolicy, fldPath *field.Path) fiel return allErrs } } + +// ValidateUpgradeConfiguration validates a UpgradeConfiguration object and collects all encountered errors +func ValidateUpgradeConfiguration(c *kubeadm.UpgradeConfiguration) field.ErrorList { + allErrs := field.ErrorList{} + if c.Apply.Patches != nil { + allErrs = append(allErrs, ValidateAbsolutePath(c.Apply.Patches.Directory, field.NewPath("patches").Child("directory"))...) + } + return allErrs +} diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go index a6095ebb870..e8deb24eaf5 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go @@ -744,11 +744,14 @@ func TestValidateMixedArguments(t *testing.T) { {[]string{"--foo=bar"}, true}, {[]string{"--config=hello"}, true}, {[]string{"--config=hello", "--ignore-preflight-errors=all"}, true}, + // Expected to succeed, --config is mixed with skip-* flags only or no other flags {[]string{"--config=hello", "--skip-token-print=true"}, true}, {[]string{"--config=hello", "--ignore-preflight-errors=baz", "--skip-token-print"}, true}, // Expected to fail, --config is mixed with the --foo flag {[]string{"--config=hello", "--ignore-preflight-errors=baz", "--foo=bar"}, false}, {[]string{"--config=hello", "--foo=bar"}, false}, + // Expected to fail, --config is mixed with the upgrade related flag + {[]string{"--config=hello", "--allow-experimental-upgrades"}, false}, } var cfgPath string @@ -760,6 +763,7 @@ func TestValidateMixedArguments(t *testing.T) { } f.String("foo", "", "flag bound to config object") f.StringSliceVar(&ignorePreflightErrors, "ignore-preflight-errors", ignorePreflightErrors, "flag not bound to config object") + f.Bool("allow-experimental-upgrades", true, "upgrade flags for plan and apply command") f.Bool("skip-token-print", false, "flag not bound to config object") f.StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file") if err := f.Parse(rt.args); err != nil { diff --git a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go index c9b8749d8af..74e8025b700 100644 --- a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go @@ -698,3 +698,201 @@ func (in *Timeouts) DeepCopy() *Timeouts { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpgradeApplyConfiguration) DeepCopyInto(out *UpgradeApplyConfiguration) { + *out = *in + if in.AllowExperimentalUpgrades != nil { + in, out := &in.AllowExperimentalUpgrades, &out.AllowExperimentalUpgrades + *out = new(bool) + **out = **in + } + if in.AllowRCUpgrades != nil { + in, out := &in.AllowRCUpgrades, &out.AllowRCUpgrades + *out = new(bool) + **out = **in + } + if in.CertificateRenewal != nil { + in, out := &in.CertificateRenewal, &out.CertificateRenewal + *out = new(bool) + **out = **in + } + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = new(bool) + **out = **in + } + if in.EtcdUpgrade != nil { + in, out := &in.EtcdUpgrade, &out.EtcdUpgrade + *out = new(bool) + **out = **in + } + if in.ForceUpgrade != nil { + in, out := &in.ForceUpgrade, &out.ForceUpgrade + *out = new(bool) + **out = **in + } + if in.IgnorePreflightErrors != nil { + in, out := &in.IgnorePreflightErrors, &out.IgnorePreflightErrors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Patches != nil { + in, out := &in.Patches, &out.Patches + *out = new(Patches) + **out = **in + } + if in.PrintConfig != nil { + in, out := &in.PrintConfig, &out.PrintConfig + *out = new(bool) + **out = **in + } + if in.SkipPhases != nil { + in, out := &in.SkipPhases, &out.SkipPhases + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradeApplyConfiguration. +func (in *UpgradeApplyConfiguration) DeepCopy() *UpgradeApplyConfiguration { + if in == nil { + return nil + } + out := new(UpgradeApplyConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpgradeConfiguration) DeepCopyInto(out *UpgradeConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.Apply.DeepCopyInto(&out.Apply) + out.Diff = in.Diff + in.Node.DeepCopyInto(&out.Node) + in.Plan.DeepCopyInto(&out.Plan) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradeConfiguration. +func (in *UpgradeConfiguration) DeepCopy() *UpgradeConfiguration { + if in == nil { + return nil + } + out := new(UpgradeConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UpgradeConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpgradeDiffConfiguration) DeepCopyInto(out *UpgradeDiffConfiguration) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradeDiffConfiguration. +func (in *UpgradeDiffConfiguration) DeepCopy() *UpgradeDiffConfiguration { + if in == nil { + return nil + } + out := new(UpgradeDiffConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpgradeNodeConfiguration) DeepCopyInto(out *UpgradeNodeConfiguration) { + *out = *in + if in.CertificateRenewal != nil { + in, out := &in.CertificateRenewal, &out.CertificateRenewal + *out = new(bool) + **out = **in + } + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = new(bool) + **out = **in + } + if in.EtcdUpgrade != nil { + in, out := &in.EtcdUpgrade, &out.EtcdUpgrade + *out = new(bool) + **out = **in + } + if in.IgnorePreflightErrors != nil { + in, out := &in.IgnorePreflightErrors, &out.IgnorePreflightErrors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SkipPhases != nil { + in, out := &in.SkipPhases, &out.SkipPhases + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Patches != nil { + in, out := &in.Patches, &out.Patches + *out = new(Patches) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradeNodeConfiguration. +func (in *UpgradeNodeConfiguration) DeepCopy() *UpgradeNodeConfiguration { + if in == nil { + return nil + } + out := new(UpgradeNodeConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpgradePlanConfiguration) DeepCopyInto(out *UpgradePlanConfiguration) { + *out = *in + if in.AllowExperimentalUpgrades != nil { + in, out := &in.AllowExperimentalUpgrades, &out.AllowExperimentalUpgrades + *out = new(bool) + **out = **in + } + if in.AllowRCUpgrades != nil { + in, out := &in.AllowRCUpgrades, &out.AllowRCUpgrades + *out = new(bool) + **out = **in + } + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = new(bool) + **out = **in + } + if in.IgnorePreflightErrors != nil { + in, out := &in.IgnorePreflightErrors, &out.IgnorePreflightErrors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.PrintConfig != nil { + in, out := &in.PrintConfig, &out.PrintConfig + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradePlanConfiguration. +func (in *UpgradePlanConfiguration) DeepCopy() *UpgradePlanConfiguration { + if in == nil { + return nil + } + out := new(UpgradePlanConfiguration) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/kubeadm/app/cmd/options/constant.go b/cmd/kubeadm/app/cmd/options/constant.go index dca246d9a22..616af497afe 100644 --- a/cmd/kubeadm/app/cmd/options/constant.go +++ b/cmd/kubeadm/app/cmd/options/constant.go @@ -145,4 +145,13 @@ const ( // AllowExperimentalAPI flag can be used to allow experimental / work in progress APIs AllowExperimentalAPI = "allow-experimental-api" + + // AllowRCUpgrades enable this flag will allow upgrading to a release candidate version of Kubernetes. + AllowRCUpgrades = "allow-release-candidate-upgrades" + + // AllowExperimentalUpgrades enable this flag will allow upgrading to an alpha/beta/release candidate version of Kubernetes. + AllowExperimentalUpgrades = "allow-experimental-upgrades" + + // PrintConfig specifies whether the cluster configuration that will be used in the upgrade should be printed or not. + PrintConfig = "print-config" ) diff --git a/cmd/kubeadm/app/cmd/phases/upgrade/node/controlplane.go b/cmd/kubeadm/app/cmd/phases/upgrade/node/controlplane.go index e396efc61d5..cb9268cdc9e 100644 --- a/cmd/kubeadm/app/cmd/phases/upgrade/node/controlplane.go +++ b/cmd/kubeadm/app/cmd/phases/upgrade/node/controlplane.go @@ -60,7 +60,7 @@ func runControlPlane() func(c workflow.RunData) error { } // otherwise, retrieve all the info required for control plane upgrade - cfg := data.Cfg() + cfg := data.InitCfg() client := data.Client() dryRun := data.DryRun() etcdUpgrade := data.EtcdUpgrade() diff --git a/cmd/kubeadm/app/cmd/phases/upgrade/node/data.go b/cmd/kubeadm/app/cmd/phases/upgrade/node/data.go index 0b920cb23ea..4c633c66f09 100644 --- a/cmd/kubeadm/app/cmd/phases/upgrade/node/data.go +++ b/cmd/kubeadm/app/cmd/phases/upgrade/node/data.go @@ -31,7 +31,8 @@ type Data interface { EtcdUpgrade() bool RenewCerts() bool DryRun() bool - Cfg() *kubeadmapi.InitConfiguration + Cfg() *kubeadmapi.UpgradeConfiguration + InitCfg() *kubeadmapi.InitConfiguration IsControlPlaneNode() bool Client() clientset.Interface IgnorePreflightErrors() sets.Set[string] diff --git a/cmd/kubeadm/app/cmd/phases/upgrade/node/data_test.go b/cmd/kubeadm/app/cmd/phases/upgrade/node/data_test.go index 28d6ae1082d..6de8db579fd 100644 --- a/cmd/kubeadm/app/cmd/phases/upgrade/node/data_test.go +++ b/cmd/kubeadm/app/cmd/phases/upgrade/node/data_test.go @@ -34,7 +34,8 @@ var _ Data = &testData{} func (t *testData) EtcdUpgrade() bool { return false } func (t *testData) RenewCerts() bool { return false } func (t *testData) DryRun() bool { return false } -func (t *testData) Cfg() *kubeadmapi.InitConfiguration { return nil } +func (t *testData) Cfg() *kubeadmapi.UpgradeConfiguration { return nil } +func (t *testData) InitCfg() *kubeadmapi.InitConfiguration { return nil } func (t *testData) IsControlPlaneNode() bool { return false } func (t *testData) Client() clientset.Interface { return nil } func (t *testData) IgnorePreflightErrors() sets.Set[string] { return nil } diff --git a/cmd/kubeadm/app/cmd/phases/upgrade/node/kubeletconfig.go b/cmd/kubeadm/app/cmd/phases/upgrade/node/kubeletconfig.go index b13cb77906d..6a6503413ec 100644 --- a/cmd/kubeadm/app/cmd/phases/upgrade/node/kubeletconfig.go +++ b/cmd/kubeadm/app/cmd/phases/upgrade/node/kubeletconfig.go @@ -57,7 +57,7 @@ func runKubeletConfigPhase() func(c workflow.RunData) error { } // otherwise, retrieve all the info required for kubelet config upgrade - cfg := data.Cfg() + cfg := data.InitCfg() dryRun := data.DryRun() // Write the configuration for the kubelet down to disk and print the generated manifests instead if dry-running. diff --git a/cmd/kubeadm/app/cmd/phases/upgrade/node/preflight.go b/cmd/kubeadm/app/cmd/phases/upgrade/node/preflight.go index 27bc04a695e..b90cdb5261a 100644 --- a/cmd/kubeadm/app/cmd/phases/upgrade/node/preflight.go +++ b/cmd/kubeadm/app/cmd/phases/upgrade/node/preflight.go @@ -60,7 +60,7 @@ func runPreflight(c workflow.RunData) error { fmt.Println("[preflight] Pulling images required for setting up a Kubernetes cluster") fmt.Println("[preflight] This might take a minute or two, depending on the speed of your internet connection") fmt.Println("[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'") - if err := preflight.RunPullImagesCheck(utilsexec.New(), data.Cfg(), data.IgnorePreflightErrors()); err != nil { + if err := preflight.RunPullImagesCheck(utilsexec.New(), data.InitCfg(), data.IgnorePreflightErrors()); err != nil { return err } } else { diff --git a/cmd/kubeadm/app/cmd/upgrade/apply.go b/cmd/kubeadm/app/cmd/upgrade/apply.go index 5e7bae5cce4..48162b671ed 100644 --- a/cmd/kubeadm/app/cmd/upgrade/apply.go +++ b/cmd/kubeadm/app/cmd/upgrade/apply.go @@ -22,6 +22,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/version" @@ -30,6 +31,7 @@ import ( utilsexec "k8s.io/utils/exec" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" "k8s.io/kubernetes/cmd/kubeadm/app/features" @@ -71,7 +73,10 @@ func newCmdApply(apf *applyPlanFlags) *cobra.Command { DisableFlagsInUseLine: true, Short: "Upgrade your Kubernetes cluster to the specified version", RunE: func(cmd *cobra.Command, args []string) error { - return runApply(flags, args) + if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil { + return err + } + return runApply(cmd.Flags(), flags, args) }, } @@ -81,7 +86,7 @@ func newCmdApply(apf *applyPlanFlags) *cobra.Command { cmd.Flags().BoolVarP(&flags.nonInteractiveMode, "yes", "y", flags.nonInteractiveMode, "Perform the upgrade and do not prompt for confirmation (non-interactive mode).") cmd.Flags().BoolVarP(&flags.force, "force", "f", flags.force, "Force upgrading although some requirements might not be met. This also implies non-interactive mode.") cmd.Flags().BoolVar(&flags.dryRun, options.DryRun, flags.dryRun, "Do not change any state, just output what actions would be performed.") - cmd.Flags().BoolVar(&flags.etcdUpgrade, "etcd-upgrade", flags.etcdUpgrade, "Perform the upgrade of etcd.") + cmd.Flags().BoolVar(&flags.etcdUpgrade, options.EtcdUpgrade, flags.etcdUpgrade, "Perform the upgrade of etcd.") cmd.Flags().BoolVar(&flags.renewCerts, options.CertificateRenewal, flags.renewCerts, "Perform the renewal of certificates used by component changed during upgrades.") options.AddPatchesFlag(cmd.Flags(), &flags.patchesDir) @@ -100,39 +105,67 @@ func newCmdApply(apf *applyPlanFlags) *cobra.Command { // - Uploads the newly used configuration to the cluster ConfigMap // - Creating the RBAC rules for the bootstrap tokens and the cluster-info ConfigMap // - Applying new CoreDNS and kube-proxy manifests -func runApply(flags *applyFlags, args []string) error { +func runApply(flagSet *pflag.FlagSet, flags *applyFlags, args []string) error { // Start with the basics, verify that the cluster is healthy and get the configuration from the cluster (using the ConfigMap) klog.V(1).Infoln("[upgrade/apply] verifying health of cluster") klog.V(1).Infoln("[upgrade/apply] retrieving configuration from cluster") - client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, flags.dryRun, true, &output.TextPrinter{}, loadConfig) + client, versionGetter, initCfg, upgradeCfg, err := enforceRequirements(flagSet, flags.applyPlanFlags, args, flags.dryRun, true, &output.TextPrinter{}) if err != nil { return err } // Validate requested and validate actual version klog.V(1).Infoln("[upgrade/apply] validating requested and actual version") - if err := configutil.NormalizeKubernetesVersion(&cfg.ClusterConfiguration); err != nil { + if err := configutil.NormalizeKubernetesVersion(&initCfg.ClusterConfiguration); err != nil { return err } // Use normalized version string in all following code. - newK8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion) + newK8sVersion, err := version.ParseSemantic(initCfg.KubernetesVersion) if err != nil { - return errors.Errorf("unable to parse normalized version %q as a semantic version", cfg.KubernetesVersion) + return errors.Errorf("unable to parse normalized version %q as a semantic version", initCfg.KubernetesVersion) } - if err := features.ValidateVersion(features.InitFeatureGates, cfg.FeatureGates, cfg.KubernetesVersion); err != nil { + if err := features.ValidateVersion(features.InitFeatureGates, initCfg.FeatureGates, initCfg.KubernetesVersion); err != nil { return err } // Enforce the version skew policies klog.V(1).Infoln("[upgrade/version] enforcing version skew policies") - if err := EnforceVersionPolicies(cfg.KubernetesVersion, newK8sVersion, flags, versionGetter); err != nil { + allowRCUpgrades, ok := cmdutil.ValueFromFlagsOrConfig(flagSet, options.AllowRCUpgrades, upgradeCfg.Apply.AllowRCUpgrades, &flags.allowRCUpgrades).(*bool) + if ok { + flags.allowRCUpgrades = *allowRCUpgrades + } else { + return cmdutil.TypeMismatchErr("allowRCUpgrades", "bool") + } + + force, ok := cmdutil.ValueFromFlagsOrConfig(flagSet, "force", upgradeCfg.Apply.ForceUpgrade, &flags.force).(*bool) + if ok { + flags.force = *force + } else { + return cmdutil.TypeMismatchErr("force", "bool") + } + + allowExperimentalUpgrades, ok := cmdutil.ValueFromFlagsOrConfig(flagSet, options.AllowExperimentalUpgrades, upgradeCfg.Apply.AllowExperimentalUpgrades, &flags.allowExperimentalUpgrades).(*bool) + if ok { + flags.allowExperimentalUpgrades = *allowExperimentalUpgrades + } else { + return cmdutil.TypeMismatchErr("allowExperimentalUpgrades", "bool") + } + + if err := EnforceVersionPolicies(initCfg.KubernetesVersion, newK8sVersion, flags, versionGetter); err != nil { return errors.Wrap(err, "[upgrade/version] FATAL") } // If the current session is interactive, ask the user whether they really want to upgrade. + dryRun, ok := cmdutil.ValueFromFlagsOrConfig(flagSet, options.DryRun, upgradeCfg.Apply.DryRun, &flags.dryRun).(*bool) + if ok { + flags.dryRun = *dryRun + } else { + return cmdutil.TypeMismatchErr("dryRun", "bool") + } + if flags.sessionIsInteractive() { if err := cmdutil.InteractivelyConfirmAction("upgrade", "Are you sure you want to proceed?", os.Stdin); err != nil { return err @@ -143,7 +176,7 @@ func runApply(flags *applyFlags, args []string) error { fmt.Println("[upgrade/prepull] Pulling images required for setting up a Kubernetes cluster") fmt.Println("[upgrade/prepull] This might take a minute or two, depending on the speed of your internet connection") fmt.Println("[upgrade/prepull] You can also perform this action in beforehand using 'kubeadm config images pull'") - if err := preflight.RunPullImagesCheck(utilsexec.New(), cfg, sets.New(cfg.NodeRegistration.IgnorePreflightErrors...)); err != nil { + if err := preflight.RunPullImagesCheck(utilsexec.New(), initCfg, sets.New(upgradeCfg.Apply.IgnorePreflightErrors...)); err != nil { return err } } else { @@ -152,14 +185,35 @@ func runApply(flags *applyFlags, args []string) error { waiter := getWaiter(flags.dryRun, client, upgrade.UpgradeManifestTimeout) + // If the config is set by flag, just overwrite it! + etcdUpgrade, ok := cmdutil.ValueFromFlagsOrConfig(flagSet, options.EtcdUpgrade, upgradeCfg.Apply.EtcdUpgrade, &flags.etcdUpgrade).(*bool) + if ok { + upgradeCfg.Apply.EtcdUpgrade = etcdUpgrade + } else { + return cmdutil.TypeMismatchErr("etcdUpgrade", "bool") + } + + renewCerts, ok := cmdutil.ValueFromFlagsOrConfig(flagSet, options.CertificateRenewal, upgradeCfg.Apply.CertificateRenewal, &flags.renewCerts).(*bool) + if ok { + upgradeCfg.Apply.CertificateRenewal = renewCerts + } else { + return cmdutil.TypeMismatchErr("renewCerts", "bool") + } + + if len(flags.patchesDir) > 0 { + upgradeCfg.Apply.Patches = &kubeadmapi.Patches{Directory: flags.patchesDir} + } else if upgradeCfg.Apply.Patches == nil { + upgradeCfg.Apply.Patches = &kubeadmapi.Patches{} + } + // Now; perform the upgrade procedure - if err := PerformControlPlaneUpgrade(flags, client, waiter, cfg); err != nil { + if err := PerformControlPlaneUpgrade(flags, client, waiter, initCfg, upgradeCfg); err != nil { return errors.Wrap(err, "[upgrade/apply] FATAL") } // Upgrade RBAC rules and addons. klog.V(1).Infoln("[upgrade/postupgrade] upgrading RBAC rules and addons") - if err := upgrade.PerformPostUpgradeTasks(client, cfg, flags.patchesDir, flags.dryRun, flags.applyPlanFlags.out); err != nil { + if err := upgrade.PerformPostUpgradeTasks(client, initCfg, upgradeCfg.Apply.Patches.Directory, flags.dryRun, flags.applyPlanFlags.out); err != nil { return errors.Wrap(err, "[upgrade/postupgrade] FATAL post-upgrade error") } @@ -169,7 +223,7 @@ func runApply(flags *applyFlags, args []string) error { } fmt.Println("") - fmt.Printf("[upgrade/successful] SUCCESS! Your cluster was upgraded to %q. Enjoy!\n", cfg.KubernetesVersion) + fmt.Printf("[upgrade/successful] SUCCESS! Your cluster was upgraded to %q. Enjoy!\n", initCfg.KubernetesVersion) fmt.Println("") fmt.Println("[upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets if you haven't already done so.") @@ -203,15 +257,14 @@ func EnforceVersionPolicies(newK8sVersionStr string, newK8sVersion *version.Vers } // PerformControlPlaneUpgrade actually performs the upgrade procedure for the cluster of your type (self-hosted or static-pod-hosted) -func PerformControlPlaneUpgrade(flags *applyFlags, client clientset.Interface, waiter apiclient.Waiter, internalcfg *kubeadmapi.InitConfiguration) error { - +func PerformControlPlaneUpgrade(flags *applyFlags, client clientset.Interface, waiter apiclient.Waiter, initCfg *kubeadmapi.InitConfiguration, upgradeCfg *kubeadmapi.UpgradeConfiguration) error { // OK, the cluster is hosted using static pods. Upgrade a static-pod hosted cluster fmt.Printf("[upgrade/apply] Upgrading your Static Pod-hosted control plane to version %q (timeout: %v)...\n", - internalcfg.KubernetesVersion, upgrade.UpgradeManifestTimeout) + initCfg.KubernetesVersion, upgrade.UpgradeManifestTimeout) if flags.dryRun { - return upgrade.DryRunStaticPodUpgrade(flags.patchesDir, internalcfg) + return upgrade.DryRunStaticPodUpgrade(upgradeCfg.Apply.Patches.Directory, initCfg) } - return upgrade.PerformStaticPodUpgrade(client, waiter, internalcfg, flags.etcdUpgrade, flags.renewCerts, flags.patchesDir) + return upgrade.PerformStaticPodUpgrade(client, waiter, initCfg, *upgradeCfg.Apply.EtcdUpgrade, *upgradeCfg.Apply.CertificateRenewal, upgradeCfg.Apply.Patches.Directory) } diff --git a/cmd/kubeadm/app/cmd/upgrade/common.go b/cmd/kubeadm/app/cmd/upgrade/common.go index 434c067d58f..5cb3acb34a8 100644 --- a/cmd/kubeadm/app/cmd/upgrade/common.go +++ b/cmd/kubeadm/app/cmd/upgrade/common.go @@ -24,21 +24,25 @@ import ( "time" "github.com/pkg/errors" + "github.com/spf13/pflag" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" fakediscovery "k8s.io/client-go/discovery/fake" clientset "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" + "k8s.io/utils/ptr" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" + "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" - "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" - kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" @@ -46,162 +50,121 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/util/output" ) -// isKubeadmConfigPresent checks if a kubeadm config type is found in the provided document map -func isKubeadmConfigPresent(docmap kubeadmapi.DocumentMap) bool { - for gvk := range docmap { - if gvk.Group == kubeadmapi.GroupName { - return true - } - } - return false -} - -// loadConfig loads configuration from a file and/or the cluster. InitConfiguration, ClusterConfiguration and (optionally) component configs -// are loaded. This function allows the component configs to be loaded from a file that contains only them. If the file contains any kubeadm types -// in it (API group "kubeadm.k8s.io" present), then the supplied file is treaded as a legacy reconfiguration style "--config" use and the -// returned bool value is set to true (the only case to be done so). -func loadConfig(cfgPath string, client clientset.Interface, skipComponentConfigs bool, printer output.Printer) (*kubeadmapi.InitConfiguration, bool, error) { - // Used for info logs here - const logPrefix = "upgrade/config" - - // The usual case here is to not have a config file, but rather load the config from the cluster. - // This is probably 90% of the time. So we handle it first. - if cfgPath == "" { - cfg, err := configutil.FetchInitConfigurationFromCluster(client, printer, logPrefix, false, skipComponentConfigs) - return cfg, false, err - } - - // Otherwise, we have a config file. Let's load it. - configBytes, err := os.ReadFile(cfgPath) - if err != nil { - return nil, false, errors.Wrapf(err, "unable to load config from file %q", cfgPath) - } - - // Split the YAML documents in the file into a DocumentMap - docmap, err := kubeadmutil.SplitYAMLDocuments(configBytes) - if err != nil { - return nil, false, err - } - - // If there are kubeadm types (API group kubeadm.k8s.io) present, we need to keep the existing behavior - // here. Basically, we have to load all of the configs from the file and none from the cluster. Configs that are - // missing from the file will be automatically regenerated by kubeadm even if they are present in the cluster. - // The resulting configs overwrite the existing cluster ones at the end of a successful upgrade apply operation. - if isKubeadmConfigPresent(docmap) { - klog.Warning("WARNING: Usage of the --config flag with kubeadm config types for reconfiguring the cluster during upgrade is not recommended!") - cfg, err := configutil.BytesToInitConfiguration(configBytes, false) - return cfg, true, err - } - - // If no kubeadm config types are present, we assume that there are manually upgraded component configs in the file. - // Hence, we load the kubeadm types from the cluster. - initCfg, err := configutil.FetchInitConfigurationFromCluster(client, printer, logPrefix, false, true) - if err != nil { - return nil, false, err - } - - // Stop here if the caller does not want us to load the component configs - if !skipComponentConfigs { - // Load the component configs with upgrades - if err := componentconfigs.FetchFromClusterWithLocalOverwrites(&initCfg.ClusterConfiguration, client, docmap); err != nil { - return nil, false, err - } - - // Now default and validate the configs - componentconfigs.Default(&initCfg.ClusterConfiguration, &initCfg.LocalAPIEndpoint, &initCfg.NodeRegistration) - if errs := componentconfigs.Validate(&initCfg.ClusterConfiguration); len(errs) != 0 { - return nil, false, errs.ToAggregate() - } - } - - return initCfg, false, nil -} - -// LoadConfigFunc is a function type that loads configuration from a file and/or the cluster. -type LoadConfigFunc func(cfgPath string, client clientset.Interface, skipComponentConfigs bool, printer output.Printer) (*kubeadmapi.InitConfiguration, bool, error) - // enforceRequirements verifies that it's okay to upgrade and then returns the variables needed for the rest of the procedure -func enforceRequirements(flags *applyPlanFlags, args []string, dryRun bool, upgradeApply bool, printer output.Printer, loadConfig LoadConfigFunc) (clientset.Interface, upgrade.VersionGetter, *kubeadmapi.InitConfiguration, error) { - client, err := getClient(flags.kubeConfigPath, dryRun) - if err != nil { - return nil, nil, nil, errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath) - } - +func enforceRequirements(flagSet *pflag.FlagSet, flags *applyPlanFlags, args []string, dryRun bool, upgradeApply bool, printer output.Printer) (clientset.Interface, upgrade.VersionGetter, *kubeadmapi.InitConfiguration, *kubeadmapi.UpgradeConfiguration, error) { // Fetch the configuration from a file or ConfigMap and validate it - _, _ = printer.Println("[upgrade/config] Loading the kubeadm configuration") + _, _ = printer.Printf("[upgrade/config] Making sure the configuration is correct:\n") - var newK8sVersion string - cfg, legacyReconfigure, err := loadConfig(flags.cfgPath, client, !upgradeApply, printer) + upgradeCfg, err := configutil.LoadUpgradeConfig(flags.cfgPath) if err != nil { - return nil, nil, nil, errors.Wrap(err, "could not load the kubeadm configuration") - } else if legacyReconfigure { - // Set the newK8sVersion to the value in the ClusterConfiguration. This is done, so that users who use the --config option - // to supply a new ClusterConfiguration don't have to specify the Kubernetes version twice, - // if they don't want to upgrade but just change a setting. - newK8sVersion = cfg.KubernetesVersion + return nil, nil, nil, nil, errors.Wrap(err, "[upgrade/upgrade config] FATAL") } - // The version arg is mandatory, during upgrade apply, unless it's specified in the config file - if upgradeApply && newK8sVersion == "" { - if err := cmdutil.ValidateExactArgNumber(args, []string{"version"}); err != nil { - return nil, nil, nil, err + // `dryRun` should be always `false` for `kubeadm plan`. + isDryRun := ptr.To(false) + printConfigCfg := upgradeCfg.Plan.PrintConfig + ignoreErrCfg := upgradeCfg.Plan.IgnorePreflightErrors + ok := false + if upgradeApply { + printConfigCfg = upgradeCfg.Apply.PrintConfig + ignoreErrCfg = upgradeCfg.Apply.IgnorePreflightErrors + isDryRun, ok = cmdutil.ValueFromFlagsOrConfig(flagSet, options.DryRun, upgradeCfg.Apply.DryRun, &dryRun).(*bool) + if !ok { + return nil, nil, nil, nil, cmdutil.TypeMismatchErr("dryRun", "bool") + } + } + + client, err := getClient(flags.kubeConfigPath, *isDryRun) + if err != nil { + return nil, nil, nil, nil, errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath) + } + + ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(flags.ignorePreflightErrors, ignoreErrCfg) + if err != nil { + return nil, nil, nil, nil, err + } + + // Also set the union of pre-flight errors to UpgradeConfiguration, to provide a consistent view of the runtime configuration. + // .Plan.IgnorePreflightErrors is not set as it's not used. + if upgradeApply { + upgradeCfg.Apply.IgnorePreflightErrors = sets.List(ignorePreflightErrorsSet) + } + + // Ensure the user is root + klog.V(1).Info("running preflight checks") + if err := runPreflightChecks(client, ignorePreflightErrorsSet, printer); err != nil { + return nil, nil, nil, nil, err + } + + initCfg, err := configutil.FetchInitConfigurationFromCluster(client, printer, "upgrade/config", false, true) + if err != nil { + if apierrors.IsNotFound(err) { + _, _ = printer.Printf("[upgrade/config] In order to upgrade, a ConfigMap called %q in the %s namespace must exist.\n", constants.KubeadmConfigConfigMap, metav1.NamespaceSystem) + _, _ = printer.Printf("[upgrade/config] Next steps:\n") + _, _ = printer.Printf("\t- OPTION 1: Run 'kubeadm config upload from-flags' and specify the same CLI arguments you passed to 'kubeadm init' when you created your control-plane.\n") + _, _ = printer.Printf("\t- OPTION 2: Run 'kubeadm config upload from-file' and specify the same config file you passed to 'kubeadm init' when you created your control-plane.\n") + _, _ = printer.Println() + err = errors.Errorf("the ConfigMap %q in the %s namespace used for getting configuration information was not found", constants.KubeadmConfigConfigMap, metav1.NamespaceSystem) + } + return nil, nil, nil, nil, errors.Wrap(err, "[upgrade/init config] FATAL") + } + + newK8sVersion := upgradeCfg.Plan.KubernetesVersion + if upgradeApply { + newK8sVersion = upgradeCfg.Apply.KubernetesVersion + // The version arg is mandatory, during upgrade apply, unless it's specified in the config file + if newK8sVersion == "" { + if err := cmdutil.ValidateExactArgNumber(args, []string{"version"}); err != nil { + return nil, nil, nil, nil, err + } } } // If option was specified in both args and config file, args will overwrite the config file. if len(args) == 1 { newK8sVersion = args[0] - if upgradeApply { - // The `upgrade apply` version always overwrites the KubernetesVersion in the returned cfg with the target - // version. While this is not the same for `upgrade plan` where the KubernetesVersion should be the old - // one (because the call to getComponentConfigVersionStates requires the currently installed version). - // This also makes the KubernetesVersion value returned for `upgrade plan` consistent as that command - // allows to not specify a target version in which case KubernetesVersion will always hold the currently - // installed one. - cfg.KubernetesVersion = newK8sVersion - } } - ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(flags.ignorePreflightErrors, cfg.NodeRegistration.IgnorePreflightErrors) - if err != nil { - return nil, nil, nil, err - } - // Also set the union of pre-flight errors to InitConfiguration, to provide a consistent view of the runtime configuration: - cfg.NodeRegistration.IgnorePreflightErrors = sets.List(ignorePreflightErrorsSet) - - // Ensure the user is root - klog.V(1).Info("running preflight checks") - if err := runPreflightChecks(client, ignorePreflightErrorsSet, printer); err != nil { - return nil, nil, nil, err + if upgradeApply { + // The `upgrade apply` version always overwrites the KubernetesVersion in the returned cfg with the target + // version. While this is not the same for `upgrade plan` where the KubernetesVersion should be the old + // one (because the call to getComponentConfigVersionStates requires the currently installed version). + // This also makes the KubernetesVersion value returned for `upgrade plan` consistent as that command + // allows to not specify a target version in which case KubernetesVersion will always hold the currently + // installed one. + initCfg.KubernetesVersion = newK8sVersion } // Run healthchecks against the cluster - if err := upgrade.CheckClusterHealth(client, &cfg.ClusterConfiguration, ignorePreflightErrorsSet, printer); err != nil { - return nil, nil, nil, errors.Wrap(err, "[upgrade/health] FATAL") + if err := upgrade.CheckClusterHealth(client, &initCfg.ClusterConfiguration, ignorePreflightErrorsSet, printer); err != nil { + return nil, nil, nil, nil, errors.Wrap(err, "[upgrade/health] FATAL") } // If features gates are passed to the command line, use it (otherwise use featureGates from configuration) if flags.featureGatesString != "" { - cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, flags.featureGatesString) + initCfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, flags.featureGatesString) if err != nil { - return nil, nil, nil, errors.Wrap(err, "[upgrade/config] FATAL") + return nil, nil, nil, nil, errors.Wrap(err, "[upgrade/config] FATAL") } } // Check if feature gate flags used in the cluster are consistent with the set of features currently supported by kubeadm - if msg := features.CheckDeprecatedFlags(&features.InitFeatureGates, cfg.FeatureGates); len(msg) > 0 { + if msg := features.CheckDeprecatedFlags(&features.InitFeatureGates, initCfg.FeatureGates); len(msg) > 0 { for _, m := range msg { printer.Printf("[upgrade/config] %s\n", m) } } // If the user told us to print this information out; do it! - if flags.printConfig { - printConfiguration(&cfg.ClusterConfiguration, os.Stdout, printer) + printConfig, ok := cmdutil.ValueFromFlagsOrConfig(flagSet, options.PrintConfig, printConfigCfg, &flags.printConfig).(*bool) + if ok && *printConfig { + printConfiguration(&initCfg.ClusterConfiguration, os.Stdout, printer) + } else if !ok { + return nil, nil, nil, nil, cmdutil.TypeMismatchErr("printConfig", "bool") } // Use a real version getter interface that queries the API server, the kubeadm client and the Kubernetes CI system for latest versions - return client, upgrade.NewOfflineVersionGetter(upgrade.NewKubeVersionGetter(client), newK8sVersion), cfg, nil + return client, upgrade.NewOfflineVersionGetter(upgrade.NewKubeVersionGetter(client), newK8sVersion), initCfg, upgradeCfg, nil } // printConfiguration prints the external version of the API to yaml diff --git a/cmd/kubeadm/app/cmd/upgrade/common_test.go b/cmd/kubeadm/app/cmd/upgrade/common_test.go index 481b724714e..d944f7e76f1 100644 --- a/cmd/kubeadm/app/cmd/upgrade/common_test.go +++ b/cmd/kubeadm/app/cmd/upgrade/common_test.go @@ -24,7 +24,8 @@ import ( "strings" "testing" - clientset "k8s.io/client-go/kubernetes" + "github.com/spf13/pflag" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" @@ -53,25 +54,18 @@ users: client-certificate-data: ` -func fakeLoadConfig(cfgPath string, client clientset.Interface, skipComponentConfigs bool, printer output.Printer) (*kubeadmapi.InitConfiguration, bool, error) { - return &kubeadmapi.InitConfiguration{}, false, nil -} - func TestEnforceRequirements(t *testing.T) { tmpDir := testutil.SetupTempDir(t) defer os.RemoveAll(tmpDir) - fullPath := filepath.Join(tmpDir, "test-config-file") f, err := os.Create(fullPath) if err != nil { t.Errorf("Unable to create test file %q: %v", fullPath, err) } defer f.Close() - if _, err = f.WriteString(testConfigToken); err != nil { t.Errorf("Unable to write test file %q: %v", fullPath, err) } - tcases := []struct { name string newK8sVersion string @@ -106,8 +100,7 @@ func TestEnforceRequirements(t *testing.T) { } for _, tt := range tcases { t.Run(tt.name, func(t *testing.T) { - _, _, _, err := enforceRequirements(&tt.flags, nil, tt.dryRun, false, &output.TextPrinter{}, fakeLoadConfig) - + _, _, _, _, err := enforceRequirements(&pflag.FlagSet{}, &tt.flags, nil, tt.dryRun, false, &output.TextPrinter{}) if err == nil && len(tt.expectedErr) != 0 { t.Error("Expected error, but got success") } @@ -123,7 +116,6 @@ func TestEnforceRequirements(t *testing.T) { if err != nil && !strings.Contains(err.Error(), expErr) { t.Fatalf("enforceRequirements returned unexpected error, expected: %s, got %v", expErr, err) } - }) } } @@ -212,52 +204,3 @@ func TestPrintConfiguration(t *testing.T) { }) } } - -func TestIsKubeadmConfigPresent(t *testing.T) { - var tcases = []struct { - name string - gvkmap kubeadmapi.DocumentMap - expected bool - }{ - { - name: " Wrong Group value", - gvkmap: kubeadmapi.DocumentMap{ - {Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}: []byte(`kind: Foo`), - }, - expected: false, - }, - { - name: "Empty Group value", - gvkmap: kubeadmapi.DocumentMap{ - {Group: "", Version: "v1", Kind: "Empty"}: []byte(`kind: Empty`), - }, - expected: false, - }, - { - name: "Nil value", - gvkmap: nil, - expected: false, - }, - { - name: "Correct Group value 1", - gvkmap: kubeadmapi.DocumentMap{ - {Group: "kubeadm.k8s.io", Version: "v1", Kind: "Empty"}: []byte(`kind: Empty`), - }, - expected: true, - }, - { - name: "Correct Group value 2", - gvkmap: kubeadmapi.DocumentMap{ - {Group: kubeadmapi.GroupName, Version: "v1", Kind: "Empty"}: []byte(`kind: Empty`), - }, - expected: true, - }, - } - for _, tt := range tcases { - t.Run(tt.name, func(t *testing.T) { - if isKubeadmConfigPresent(tt.gvkmap) != tt.expected { - t.Error("unexpected result") - } - }) - } -} diff --git a/cmd/kubeadm/app/cmd/upgrade/diff.go b/cmd/kubeadm/app/cmd/upgrade/diff.go index 0b2a46f311f..f30439d456b 100644 --- a/cmd/kubeadm/app/cmd/upgrade/diff.go +++ b/cmd/kubeadm/app/cmd/upgrade/diff.go @@ -23,13 +23,15 @@ import ( "github.com/pkg/errors" "github.com/pmezard/go-difflib/difflib" "github.com/spf13/cobra" + "github.com/spf13/pflag" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/version" - client "k8s.io/client-go/kubernetes" + clientset "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" "k8s.io/kubernetes/cmd/kubeadm/app/constants" @@ -37,6 +39,7 @@ import ( kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" + "k8s.io/kubernetes/cmd/kubeadm/app/util/output" ) type diffFlags struct { @@ -74,7 +77,11 @@ func newCmdDiff(out io.Writer) *cobra.Command { flags.schedulerManifestPath); err != nil { return err } - return runDiff(flags, args) + + if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil { + return err + } + return runDiff(cmd.Flags(), flags, args, configutil.FetchInitConfigurationFromCluster) }, } @@ -107,50 +114,49 @@ func validateManifestsPath(manifests ...string) (err error) { return nil } -func runDiff(flags *diffFlags, args []string) error { - var err error - var cfg *kubeadmapi.InitConfiguration - if flags.cfgPath != "" { - cfg, err = configutil.LoadInitConfigurationFromFile(flags.cfgPath, configutil.LoadOrDefaultConfigurationOptions{ - SkipCRIDetect: true, - }) - } else { - var client client.Interface - client, err = kubeconfigutil.ClientSetFromFile(flags.kubeConfigPath) - if err != nil { - return errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath) - } - cfg, err = configutil.FetchInitConfigurationFromCluster(client, nil, "upgrade/diff", false, false) +// FetchInitConfigurationFunc defines the signature of the function which will fetch InitConfiguration from cluster. +type FetchInitConfigurationFunc func(client clientset.Interface, printer output.Printer, logPrefix string, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) + +func runDiff(fs *pflag.FlagSet, flags *diffFlags, args []string, fetchInitConfigurationFromCluster FetchInitConfigurationFunc) error { + upgradeCfg, err := configutil.LoadUpgradeConfig(flags.cfgPath) + if err != nil { + return err } + client, err := kubeconfigutil.ClientSetFromFile(flags.kubeConfigPath) + if err != nil { + return errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath) + } + initCfg, err := fetchInitConfigurationFromCluster(client, &output.TextPrinter{}, "upgrade/diff", false, true) if err != nil { return err } - // If the version is specified in config file, pick up that value. - if cfg.KubernetesVersion != "" { - flags.newK8sVersionStr = cfg.KubernetesVersion + // Pick up the version from the ClusterConfiguration. + if initCfg.KubernetesVersion != "" { + flags.newK8sVersionStr = initCfg.KubernetesVersion + } + if upgradeCfg.Diff.KubernetesVersion != "" { + flags.newK8sVersionStr = upgradeCfg.Diff.KubernetesVersion } - // If the new version is already specified in config file, version arg is optional. + // Version must be specified via version arg if it's not set in ClusterConfiguration. if flags.newK8sVersionStr == "" { if err := cmdutil.ValidateExactArgNumber(args, []string{"version"}); err != nil { return err } } - // If option was specified in both args and config file, args will overwrite the config file. if len(args) == 1 { flags.newK8sVersionStr = args[0] } - _, err = version.ParseSemantic(flags.newK8sVersionStr) if err != nil { return err } - cfg.ClusterConfiguration.KubernetesVersion = flags.newK8sVersionStr + initCfg.ClusterConfiguration.KubernetesVersion = flags.newK8sVersionStr - specs := controlplane.GetStaticPodSpecs(&cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, nil) + specs := controlplane.GetStaticPodSpecs(&initCfg.ClusterConfiguration, &initCfg.LocalAPIEndpoint, nil) for spec, pod := range specs { var path string switch spec { @@ -164,7 +170,6 @@ func runDiff(flags *diffFlags, args []string) error { klog.Errorf("[diff] unknown spec %v", spec) continue } - newManifest, err := kubeadmutil.MarshalToYaml(&pod, corev1.SchemeGroupVersion) if err != nil { return err @@ -183,7 +188,7 @@ func runDiff(flags *diffFlags, args []string) error { B: difflib.SplitLines(string(newManifest)), FromFile: path, ToFile: "new manifest", - Context: flags.contextLines, + Context: cmdutil.ValueFromFlagsOrConfig(fs, "context-lines", upgradeCfg.Diff.DiffContextLines, flags.contextLines).(int), } difflib.WriteUnifiedDiff(flags.out, diff) diff --git a/cmd/kubeadm/app/cmd/upgrade/diff_test.go b/cmd/kubeadm/app/cmd/upgrade/diff_test.go index b9aa325a410..1acdf0a1c93 100644 --- a/cmd/kubeadm/app/cmd/upgrade/diff_test.go +++ b/cmd/kubeadm/app/cmd/upgrade/diff_test.go @@ -24,8 +24,10 @@ import ( "github.com/pkg/errors" - kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3" - "k8s.io/kubernetes/cmd/kubeadm/app/constants" + clientset "k8s.io/client-go/kubernetes" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta4" + "k8s.io/kubernetes/cmd/kubeadm/app/util/output" ) func createTestRunDiffFile(contents []byte) (string, error) { @@ -42,23 +44,25 @@ func createTestRunDiffFile(contents []byte) (string, error) { return file.Name(), nil } -func TestRunDiff(t *testing.T) { - currentVersion := "v" + constants.CurrentKubernetesVersion.String() +func fakeFetchInitConfig(client clientset.Interface, printer output.Printer, logPrefix string, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) { + return &kubeadmapi.InitConfiguration{ + ClusterConfiguration: kubeadmapi.ClusterConfiguration{ + KubernetesVersion: "v1.0.1", + }, + }, nil +} +func TestRunDiff(t *testing.T) { // create a temporary file with valid ClusterConfiguration testUpgradeDiffConfigContents := []byte(fmt.Sprintf(` apiVersion: %s -kind: InitConfiguration ---- -apiVersion: %[1]s -kind: ClusterConfiguration -kubernetesVersion: %s`, kubeadmapiv1.SchemeGroupVersion.String(), currentVersion)) +kind: UpgradeConfiguration +contextLines: 4`, kubeadmapiv1.SchemeGroupVersion.String())) testUpgradeDiffConfig, err := createTestRunDiffFile(testUpgradeDiffConfigContents) if err != nil { t.Fatal(err) } defer os.Remove(testUpgradeDiffConfig) - // create a temporary manifest file with dummy contents testUpgradeDiffManifestContents := []byte("some-contents") testUpgradeDiffManifest, err := createTestRunDiffFile(testUpgradeDiffManifestContents) @@ -67,6 +71,13 @@ kubernetesVersion: %s`, kubeadmapiv1.SchemeGroupVersion.String(), currentVersion } defer os.Remove(testUpgradeDiffManifest) + kubeConfigPath, err := createTestRunDiffFile([]byte(testConfigToken)) + if err != nil { + t.Fatal(err) + } + //nolint:errcheck + defer os.Remove(kubeConfigPath) + flags := &diffFlags{ cfgPath: "", out: io.Discard, @@ -118,12 +129,14 @@ kubernetesVersion: %s`, kubeadmapiv1.SchemeGroupVersion.String(), currentVersion for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { flags.cfgPath = tc.cfgPath + flags.kubeConfigPath = kubeConfigPath + cmd := newCmdDiff(os.Stdout) if tc.setManifestPath { flags.apiServerManifestPath = tc.manifestPath flags.controllerManagerManifestPath = tc.manifestPath flags.schedulerManifestPath = tc.manifestPath } - if err := runDiff(flags, tc.args); (err != nil) != tc.expectedError { + if err := runDiff(cmd.Flags(), flags, tc.args, fakeFetchInitConfig); (err != nil) != tc.expectedError { t.Fatalf("expected error: %v, saw: %v, error: %v", tc.expectedError, (err != nil), err) } }) diff --git a/cmd/kubeadm/app/cmd/upgrade/node.go b/cmd/kubeadm/app/cmd/upgrade/node.go index 9998436edb2..beccc6e7d38 100644 --- a/cmd/kubeadm/app/cmd/upgrade/node.go +++ b/cmd/kubeadm/app/cmd/upgrade/node.go @@ -32,6 +32,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" phases "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/upgrade/node" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" + cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" "k8s.io/kubernetes/cmd/kubeadm/app/constants" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" ) @@ -40,6 +41,7 @@ import ( // Please note that this structure includes the public kubeadm config API, but only a subset of the options // supported by this api will be exposed as a flag. type nodeOptions struct { + cfgPath string kubeConfigPath string etcdUpgrade bool renewCerts bool @@ -57,7 +59,8 @@ type nodeData struct { etcdUpgrade bool renewCerts bool dryRun bool - cfg *kubeadmapi.InitConfiguration + cfg *kubeadmapi.UpgradeConfiguration + initCfg *kubeadmapi.InitConfiguration isControlPlaneNode bool client clientset.Interface patchesDir string @@ -75,6 +78,10 @@ func newCmdNode(out io.Writer) *cobra.Command { Use: "node", Short: "Upgrade commands for a node in the cluster", RunE: func(cmd *cobra.Command, args []string) error { + if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil { + return err + } + return nodeRunner.Run(args) }, Args: cobra.NoArgs, @@ -83,6 +90,7 @@ func newCmdNode(out io.Writer) *cobra.Command { // adds flags to the node command // flags could be eventually inherited by the sub-commands automatically generated for phases addUpgradeNodeFlags(cmd.Flags(), nodeOptions) + options.AddConfigFlag(cmd.Flags(), &nodeOptions.cfgPath) options.AddPatchesFlag(cmd.Flags(), &nodeOptions.patchesDir) // initialize the workflow runner with the list of phases @@ -93,7 +101,15 @@ func newCmdNode(out io.Writer) *cobra.Command { // sets the data builder function, that will be used by the runner // both when running the entire workflow or single phases nodeRunner.SetDataInitializer(func(cmd *cobra.Command, args []string) (workflow.RunData, error) { - return newNodeData(cmd, args, nodeOptions, out) + data, err := newNodeData(cmd, args, nodeOptions, out) + if err != nil { + return nil, err + } + // If the flag for skipping phases was empty, use the values from config + if len(nodeRunner.Options.SkipPhases) == 0 { + nodeRunner.Options.SkipPhases = data.cfg.Node.SkipPhases + } + return data, nil }) // binds the Runner to kubeadm upgrade node command by altering @@ -124,50 +140,69 @@ func addUpgradeNodeFlags(flagSet *flag.FlagSet, nodeOptions *nodeOptions) { // newNodeData returns a new nodeData struct to be used for the execution of the kubeadm upgrade node workflow. // This func takes care of validating nodeOptions passed to the command, and then it converts // options into the internal InitConfiguration type that is used as input all the phases in the kubeadm upgrade node workflow -func newNodeData(cmd *cobra.Command, args []string, options *nodeOptions, out io.Writer) (*nodeData, error) { +func newNodeData(cmd *cobra.Command, args []string, nodeOptions *nodeOptions, out io.Writer) (*nodeData, error) { // Checks if a node is a control-plane node by looking up the kube-apiserver manifest file isControlPlaneNode := true filepath := constants.GetStaticPodFilepath(constants.KubeAPIServer, constants.GetStaticPodDirectory()) if _, err := os.Stat(filepath); os.IsNotExist(err) { isControlPlaneNode = false } - if len(options.kubeConfigPath) == 0 { + if len(nodeOptions.kubeConfigPath) == 0 { // Update the kubeconfig path depending on whether this is a control plane node or not. - options.kubeConfigPath = constants.GetKubeletKubeConfigPath() + nodeOptions.kubeConfigPath = constants.GetKubeletKubeConfigPath() if isControlPlaneNode { - options.kubeConfigPath = constants.GetAdminKubeConfigPath() + nodeOptions.kubeConfigPath = constants.GetAdminKubeConfigPath() } } - client, err := getClient(options.kubeConfigPath, options.dryRun) + upgradeCfg, err := configutil.LoadUpgradeConfig(nodeOptions.cfgPath) if err != nil { - return nil, errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", options.kubeConfigPath) + return nil, err } + + dryRun, ok := cmdutil.ValueFromFlagsOrConfig(cmd.Flags(), options.DryRun, upgradeCfg.Node.DryRun, &nodeOptions.dryRun).(*bool) + if !ok { + return nil, cmdutil.TypeMismatchErr("dryRun", "bool") + } + client, err := getClient(nodeOptions.kubeConfigPath, *dryRun) + if err != nil { + return nil, errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", nodeOptions.kubeConfigPath) + } + // Fetches the cluster configuration // NB in case of control-plane node, we are reading all the info for the node; in case of NOT control-plane node // (worker node), we are not reading local API address and the CRI socket from the node object - cfg, err := configutil.FetchInitConfigurationFromCluster(client, nil, "upgrade", !isControlPlaneNode, false) + initCfg, err := configutil.FetchInitConfigurationFromCluster(client, nil, "upgrade", !isControlPlaneNode, false) if err != nil { return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap") } - ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(options.ignorePreflightErrors, cfg.NodeRegistration.IgnorePreflightErrors) + ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(nodeOptions.ignorePreflightErrors, initCfg.NodeRegistration.IgnorePreflightErrors) if err != nil { return nil, err } // Also set the union of pre-flight errors to JoinConfiguration, to provide a consistent view of the runtime configuration: - cfg.NodeRegistration.IgnorePreflightErrors = sets.List(ignorePreflightErrorsSet) + initCfg.NodeRegistration.IgnorePreflightErrors = sets.List(ignorePreflightErrorsSet) + + var patchesDir string + if upgradeCfg.Node.Patches != nil { + patchesDir = cmdutil.ValueFromFlagsOrConfig(cmd.Flags(), options.Patches, upgradeCfg.Node.Patches.Directory, nodeOptions.patchesDir).(string) + } else { + patchesDir = nodeOptions.patchesDir + } + return &nodeData{ - etcdUpgrade: options.etcdUpgrade, - renewCerts: options.renewCerts, - dryRun: options.dryRun, - cfg: cfg, + cfg: upgradeCfg, + dryRun: *dryRun, + initCfg: initCfg, client: client, isControlPlaneNode: isControlPlaneNode, - patchesDir: options.patchesDir, ignorePreflightErrors: ignorePreflightErrorsSet, - kubeConfigPath: options.kubeConfigPath, + kubeConfigPath: nodeOptions.kubeConfigPath, outputWriter: out, + patchesDir: patchesDir, + etcdUpgrade: *cmdutil.ValueFromFlagsOrConfig(cmd.Flags(), options.EtcdUpgrade, upgradeCfg.Node.EtcdUpgrade, &nodeOptions.etcdUpgrade).(*bool), + renewCerts: *cmdutil.ValueFromFlagsOrConfig(cmd.Flags(), options.CertificateRenewal, upgradeCfg.Node.CertificateRenewal, &nodeOptions.renewCerts).(*bool), }, nil } @@ -186,11 +221,16 @@ func (d *nodeData) RenewCerts() bool { return d.renewCerts } -// Cfg returns initConfiguration. -func (d *nodeData) Cfg() *kubeadmapi.InitConfiguration { +// Cfg returns upgradeConfiguration. +func (d *nodeData) Cfg() *kubeadmapi.UpgradeConfiguration { return d.cfg } +// InitCfg returns the InitConfiguration. +func (d *nodeData) InitCfg() *kubeadmapi.InitConfiguration { + return d.initCfg +} + // IsControlPlaneNode returns the isControlPlaneNode flag. func (d *nodeData) IsControlPlaneNode() bool { return d.isControlPlaneNode diff --git a/cmd/kubeadm/app/cmd/upgrade/plan.go b/cmd/kubeadm/app/cmd/upgrade/plan.go index ef1bd190750..a60c98e35b8 100644 --- a/cmd/kubeadm/app/cmd/upgrade/plan.go +++ b/cmd/kubeadm/app/cmd/upgrade/plan.go @@ -27,14 +27,17 @@ import ( "github.com/lithammer/dedent" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/version" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/klog/v2" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" outputapischeme "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/scheme" outputapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha3" + "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs" "k8s.io/kubernetes/cmd/kubeadm/app/constants" @@ -64,13 +67,16 @@ func newCmdPlan(apf *applyPlanFlags) *cobra.Command { Use: "plan [version] [flags]", Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable.", Long: upgradePlanLongDesc, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { printer, err := outputFlags.ToPrinter() if err != nil { return errors.Wrap(err, "could not construct output printer") } - return runPlan(flags, args, printer) + if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil { + return err + } + return runPlan(cmd.Flags(), flags, args, printer) }, } @@ -91,29 +97,41 @@ func newComponentUpgradePlan(name, currentVersion, newVersion string) outputapiv } // runPlan takes care of outputting available versions to upgrade to for the user -func runPlan(flags *planFlags, args []string, printer output.Printer) error { +func runPlan(flagSet *pflag.FlagSet, flags *planFlags, args []string, printer output.Printer) error { // Start with the basics, verify that the cluster is healthy, build a client and a versionGetter. Never dry-run when planning. klog.V(1).Infoln("[upgrade/plan] verifying health of cluster") klog.V(1).Infoln("[upgrade/plan] retrieving configuration from cluster") - client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, false, false, printer, loadConfig) + client, versionGetter, initCfg, upgradeCfg, err := enforceRequirements(flagSet, flags.applyPlanFlags, args, false, false, printer) if err != nil { return err } // Currently this is the only method we have for distinguishing // external etcd vs static pod etcd - isExternalEtcd := cfg.Etcd.External != nil + isExternalEtcd := initCfg.Etcd.External != nil // Compute which upgrade possibilities there are klog.V(1).Infoln("[upgrade/plan] computing upgrade possibilities") - availUpgrades, err := upgrade.GetAvailableUpgrades(versionGetter, flags.allowExperimentalUpgrades, flags.allowRCUpgrades, isExternalEtcd, client, constants.GetStaticPodDirectory(), printer) + + // flags are respected while keeping the configuration file not changed. + allowRCUpgrades, ok := cmdutil.ValueFromFlagsOrConfig(flagSet, options.AllowRCUpgrades, upgradeCfg.Plan.AllowRCUpgrades, &flags.allowRCUpgrades).(*bool) + if !ok { + return cmdutil.TypeMismatchErr("allowRCUpgrades", "bool") + } + + allowExperimentalUpgrades, ok := cmdutil.ValueFromFlagsOrConfig(flagSet, options.AllowExperimentalUpgrades, upgradeCfg.Plan.AllowExperimentalUpgrades, &flags.allowExperimentalUpgrades).(*bool) + if !ok { + return cmdutil.TypeMismatchErr("allowExperimentalUpgrades", "bool") + } + + availUpgrades, err := upgrade.GetAvailableUpgrades(versionGetter, *allowExperimentalUpgrades, *allowRCUpgrades, isExternalEtcd, client, constants.GetStaticPodDirectory(), printer) if err != nil { return errors.Wrap(err, "[upgrade/versions] FATAL") } // Fetch the current state of the component configs klog.V(1).Infoln("[upgrade/plan] analysing component config version states") - configVersionStates, err := componentconfigs.GetVersionStates(&cfg.ClusterConfiguration, client) + configVersionStates, err := componentconfigs.GetVersionStates(&initCfg.ClusterConfiguration, client) if err != nil { return errors.WithMessage(err, "[upgrade/versions] FATAL") } diff --git a/cmd/kubeadm/app/cmd/util/cmdutil.go b/cmd/kubeadm/app/cmd/util/cmdutil.go index 63e7b0ca178..de898102a5f 100644 --- a/cmd/kubeadm/app/cmd/util/cmdutil.go +++ b/cmd/kubeadm/app/cmd/util/cmdutil.go @@ -30,6 +30,7 @@ import ( clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "k8s.io/klog/v2" + "k8s.io/utils/ptr" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" @@ -135,6 +136,18 @@ func ValueFromFlagsOrConfig(flagSet *pflag.FlagSet, name string, cfgValue interf if flagSet.Changed(name) { return flagValue } + + // covert the nil to false if this is a bool, this will help to get rid of nil dereference error. + cfg, ok := cfgValue.(*bool) + if ok && cfg == nil { + return ptr.To(false) + } + // assume config has all the defaults set correctly. return cfgValue } + +// TypeMismatchErr return an error which indicates how the type is mismatched. +func TypeMismatchErr(opt, rType string) error { + return errors.Errorf("type mismatch, %s is expected to be a pointer to %s", opt, rType) +} diff --git a/cmd/kubeadm/app/cmd/util/cmdutil_test.go b/cmd/kubeadm/app/cmd/util/cmdutil_test.go index d46bfb45af0..cad0322736c 100644 --- a/cmd/kubeadm/app/cmd/util/cmdutil_test.go +++ b/cmd/kubeadm/app/cmd/util/cmdutil_test.go @@ -128,6 +128,12 @@ func TestValueFromFlagsOrConfig(t *testing.T) { flagValue: false, expected: false, }, + { + name: "nil bool is converted to false", + cfg: (*bool)(nil), + flagValue: false, + expected: false, + }, } for _, tt := range tests { type options struct { @@ -140,11 +146,15 @@ func TestValueFromFlagsOrConfig(t *testing.T) { fs.BoolVar(&fakeOptions.bar, "bar", false, "") t.Run(tt.name, func(t *testing.T) { - err := fs.Set(tt.flag, fmt.Sprintf("%v", tt.flagValue)) - if err != nil { - t.Fatalf("failed to set the value of the flag %v", tt.flagValue) + if tt.flag != "" { + if err := fs.Set(tt.flag, fmt.Sprintf("%v", tt.flagValue)); err != nil { + t.Fatalf("failed to set the value of the flag %v", tt.flagValue) + } } actualResult := ValueFromFlagsOrConfig(&fs, tt.flag, tt.cfg, tt.flagValue) + if result, ok := actualResult.(*bool); ok { + actualResult = *result + } if actualResult != tt.expected { t.Errorf( "failed ValueFromFlagsOrConfig:\n\texpected: %s\n\t actual: %s", diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index 3dc9cc6dc01..2d19317878c 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -452,6 +452,8 @@ const ( EtcdUserName string = "kubeadm-etcd" // ServiceAccountKeyReadersGroupName is the group of users that are allowed to read the service account private key. ServiceAccountKeyReadersGroupName string = "kubeadm-sa-key-readers" + // UpgradeConfigurationKind is the string kind value for the UpgradeConfiguration struct + UpgradeConfigurationKind = "UpgradeConfiguration" ) var ( diff --git a/cmd/kubeadm/app/util/config/common.go b/cmd/kubeadm/app/util/config/common.go index 36f445fe4c4..f073ef80872 100644 --- a/cmd/kubeadm/app/util/config/common.go +++ b/cmd/kubeadm/app/util/config/common.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package config contains utilities for managing the kubeadm configuration API. package config import ( @@ -490,3 +491,13 @@ func defaultEmptyMigrateMutators() migrateMutators { return *mutators } + +// isKubeadmConfigPresent checks if a kubeadm config type is found in the provided document map +func isKubeadmConfigPresent(docmap kubeadmapi.DocumentMap) bool { + for gvk := range docmap { + if gvk.Group == kubeadmapi.GroupName { + return true + } + } + return false +} diff --git a/cmd/kubeadm/app/util/config/common_test.go b/cmd/kubeadm/app/util/config/common_test.go index 62b87a77014..ecee207cf0b 100644 --- a/cmd/kubeadm/app/util/config/common_test.go +++ b/cmd/kubeadm/app/util/config/common_test.go @@ -858,3 +858,52 @@ func TestDefaultMigrateMutators(t *testing.T) { }) } } + +func TestIsKubeadmConfigPresent(t *testing.T) { + var tcases = []struct { + name string + gvkmap kubeadmapi.DocumentMap + expected bool + }{ + { + name: " Wrong Group value", + gvkmap: kubeadmapi.DocumentMap{ + {Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}: []byte(`kind: Foo`), + }, + expected: false, + }, + { + name: "Empty Group value", + gvkmap: kubeadmapi.DocumentMap{ + {Group: "", Version: "v1", Kind: "Empty"}: []byte(`kind: Empty`), + }, + expected: false, + }, + { + name: "Nil value", + gvkmap: nil, + expected: false, + }, + { + name: "Correct Group value 1", + gvkmap: kubeadmapi.DocumentMap{ + {Group: "kubeadm.k8s.io", Version: "v1", Kind: "Empty"}: []byte(`kind: Empty`), + }, + expected: true, + }, + { + name: "Correct Group value 2", + gvkmap: kubeadmapi.DocumentMap{ + {Group: kubeadmapi.GroupName, Version: "v1", Kind: "Empty"}: []byte(`kind: Empty`), + }, + expected: true, + }, + } + for _, tt := range tcases { + t.Run(tt.name, func(t *testing.T) { + if isKubeadmConfigPresent(tt.gvkmap) != tt.expected { + t.Error("unexpected result") + } + }) + } +} diff --git a/cmd/kubeadm/app/util/config/upgradeconfiguration.go b/cmd/kubeadm/app/util/config/upgradeconfiguration.go new file mode 100644 index 00000000000..36299e4da71 --- /dev/null +++ b/cmd/kubeadm/app/util/config/upgradeconfiguration.go @@ -0,0 +1,158 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "os" + + "github.com/pkg/errors" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog/v2" + kubeproxyconfig "k8s.io/kube-proxy/config/v1alpha1" + kubeletconfig "k8s.io/kubelet/config/v1beta1" + + 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/v1beta4" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" + "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + "k8s.io/kubernetes/cmd/kubeadm/app/util/config/strict" +) + +var componentCfgGV = sets.New(kubeproxyconfig.GroupName, kubeletconfig.GroupName) + +// DefaultUpgradeConfiguration return a default UpgradeConfiguration +func DefaultUpgradeConfiguration() (*kubeadmapi.UpgradeConfiguration, error) { + versionedCfg := &kubeadmapiv1.UpgradeConfiguration{} + kubeadmscheme.Scheme.Default(versionedCfg) + cfg := &kubeadmapi.UpgradeConfiguration{} + if err := kubeadmscheme.Scheme.Convert(versionedCfg, cfg, nil); err != nil { + return nil, errors.Wrap(err, "could not prepare a defaulted UpgradeConfiguration") + } + return cfg, nil +} + +// documentMapToUpgradeConfiguration takes a map between GVKs and YAML documents (as returned by SplitYAMLDocuments), +// finds a UpgradeConfiguration, decodes it, dynamically defaults it and then validates it prior to return. +func documentMapToUpgradeConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecated bool) (*kubeadmapi.UpgradeConfiguration, error) { + var internalcfg *kubeadmapi.UpgradeConfiguration + + for gvk, bytes := range gvkmap { + // check if this version is supported and possibly not deprecated + if err := validateSupportedVersion(gvk.GroupVersion(), allowDeprecated, true); err != nil { + return nil, err + } + + // verify the validity of the YAML + if err := strict.VerifyUnmarshalStrict([]*runtime.Scheme{kubeadmscheme.Scheme}, gvk, bytes); err != nil { + klog.Warning(err.Error()) + } + + if kubeadmutil.GroupVersionKindsHasInitConfiguration(gvk) || kubeadmutil.GroupVersionKindsHasClusterConfiguration(gvk) { + klog.Warningf("[config] WARNING: YAML document with GroupVersionKind %v is deprecated for upgrade, please use config file with kind of UpgradeConfiguration instead \n", gvk) + continue + } + + if kubeadmutil.GroupVersionKindsHasUpgradeConfiguration(gvk) { + // Set internalcfg to an empty struct value the deserializer will populate + internalcfg = &kubeadmapi.UpgradeConfiguration{} + // Decode the bytes into the internal struct. Under the hood, the bytes will be unmarshalled into the + // right external version, defaulted, and converted into the internal version. + if err := runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(), bytes, internalcfg); err != nil { + return nil, err + } + continue + } + + // If the group is neither a kubeadm core type or of a supported component config group, we dump a warning about it being ignored + if !componentconfigs.Scheme.IsGroupRegistered(gvk.Group) { + klog.Warningf("[config] WARNING: Ignored YAML document with GroupVersionKind %v\n", gvk) + } + } + + // If UpgradeConfiguration wasn't given, default it by creating an external struct instance, default it and convert into the internal type + if internalcfg == nil { + extinitcfg := &kubeadmapiv1.UpgradeConfiguration{} + kubeadmscheme.Scheme.Default(extinitcfg) + // Set upgradeCfg to an empty struct value the deserializer will populate + internalcfg = &kubeadmapi.UpgradeConfiguration{} + if err := kubeadmscheme.Scheme.Convert(extinitcfg, internalcfg, nil); err != nil { + return nil, err + } + } + + // Validates cfg + if err := validation.ValidateUpgradeConfiguration(internalcfg).ToAggregate(); err != nil { + return nil, err + } + + return internalcfg, nil +} + +// DocMapToUpgradeConfiguration converts documentMap to an internal, defaulted and validated UpgradeConfiguration object. +// The map may contain many different YAML documents. These YAML documents are parsed one-by-one +// and well-known ComponentConfig GroupVersionKinds are stored inside of the internal UpgradeConfiguration struct. +// The resulting UpgradeConfiguration is then dynamically defaulted and validated prior to return. +func DocMapToUpgradeConfiguration(gvkmap kubeadmapi.DocumentMap) (*kubeadmapi.UpgradeConfiguration, error) { + return documentMapToUpgradeConfiguration(gvkmap, false) +} + +// LoadUpgradeConfig loads UpgradeConfiguration from a file. +func LoadUpgradeConfig(cfgPath string) (*kubeadmapi.UpgradeConfiguration, error) { + var err error + var upgradeCfg *kubeadmapi.UpgradeConfiguration + if cfgPath == "" { + if upgradeCfg, err = DefaultUpgradeConfiguration(); err != nil { + return nil, err + } + return upgradeCfg, nil + } + + // Otherwise, we have a config file. Let's load it. + configBytes, err := os.ReadFile(cfgPath) + if err != nil { + return nil, errors.Wrapf(err, "unable to load config from file %q", cfgPath) + } + + // Split the YAML documents in the file into a DocumentMap + docmap, err := kubeadmutil.SplitYAMLDocuments(configBytes) + if err != nil { + return nil, err + } + + // Convert documentMap to internal UpgradeConfiguration, InitConfiguration and ClusterConfiguration from config file will be ignored. + // Upgrade should respect the cluster configuration from the existing cluster, re-configure the cluster with a InitConfiguration and + // ClusterConfiguration from the config file is not allowed for upgrade. + if isKubeadmConfigPresent(docmap) { + if upgradeCfg, err = DocMapToUpgradeConfiguration(docmap); err != nil { + return nil, err + } + } + + // Check is there any component configs defined in the config file. + for gvk := range docmap { + if componentCfgGV.Has(gvk.Group) { + klog.Warningf("[config] WARNING: YAML document with Component Configs %v is deprecated for upgrade and will be ignored \n", gvk.Group) + continue + } + } + + return upgradeCfg, nil +} diff --git a/cmd/kubeadm/app/util/config/upgradeconfiguration_test.go b/cmd/kubeadm/app/util/config/upgradeconfiguration_test.go new file mode 100644 index 00000000000..73bc7494774 --- /dev/null +++ b/cmd/kubeadm/app/util/config/upgradeconfiguration_test.go @@ -0,0 +1,104 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/yaml" + + "github.com/google/go-cmp/cmp" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta4" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" +) + +func TestDocMapToUpgradeConfiguration(t *testing.T) { + tests := []struct { + name string + cfg kubeadmapiv1.UpgradeConfiguration + expectedCfg kubeadmapi.UpgradeConfiguration + }{ + { + name: "default config is set correctly", + cfg: kubeadmapiv1.UpgradeConfiguration{ + TypeMeta: metav1.TypeMeta{ + APIVersion: kubeadmapiv1.SchemeGroupVersion.String(), + Kind: constants.UpgradeConfigurationKind, + }, + }, + expectedCfg: kubeadmapi.UpgradeConfiguration{ + Apply: kubeadmapi.UpgradeApplyConfiguration{ + CertificateRenewal: ptr.To(true), + EtcdUpgrade: ptr.To(true), + }, + Node: kubeadmapi.UpgradeNodeConfiguration{ + CertificateRenewal: ptr.To(true), + EtcdUpgrade: ptr.To(true), + }, + }, + }, + { + name: "cfg has part of fields configured", + cfg: kubeadmapiv1.UpgradeConfiguration{ + Apply: kubeadmapiv1.UpgradeApplyConfiguration{ + CertificateRenewal: ptr.To(false), + }, + Node: kubeadmapiv1.UpgradeNodeConfiguration{ + EtcdUpgrade: ptr.To(false), + }, + TypeMeta: metav1.TypeMeta{ + APIVersion: kubeadmapiv1.SchemeGroupVersion.String(), + Kind: constants.UpgradeConfigurationKind, + }, + }, + expectedCfg: kubeadmapi.UpgradeConfiguration{ + Apply: kubeadmapi.UpgradeApplyConfiguration{ + CertificateRenewal: ptr.To(false), + EtcdUpgrade: ptr.To(true), + }, + Node: kubeadmapi.UpgradeNodeConfiguration{ + CertificateRenewal: ptr.To(true), + EtcdUpgrade: ptr.To(false), + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + b, err := yaml.Marshal(tc.cfg) + if err != nil { + t.Fatalf("unexpected error while marshalling to YAML: %v", err) + } + docmap, err := kubeadmutil.SplitYAMLDocuments(b) + if err != nil { + t.Fatalf("Unexpect error of SplitYAMLDocuments: %v", err) + } + cfg, err := DocMapToUpgradeConfiguration(docmap) + if err != nil { + t.Fatalf("unexpected error of DocMapToUpgradeConfiguration: %v\nconfig: %s", err, string(b)) + } + if diff := cmp.Diff(*cfg, tc.expectedCfg); diff != "" { + t.Fatalf("DocMapToUpgradeConfiguration returned unexpected diff (-want,+got):\n%s", diff) + } + }) + } +} diff --git a/cmd/kubeadm/app/util/marshal.go b/cmd/kubeadm/app/util/marshal.go index c3bc36f5f79..c82e3022016 100644 --- a/cmd/kubeadm/app/util/marshal.go +++ b/cmd/kubeadm/app/util/marshal.go @@ -151,3 +151,8 @@ func GroupVersionKindsHasJoinConfiguration(gvks ...schema.GroupVersionKind) bool func GroupVersionKindsHasResetConfiguration(gvks ...schema.GroupVersionKind) bool { return GroupVersionKindsHasKind(gvks, constants.ResetConfigurationKind) } + +// GroupVersionKindsHasUpgradeConfiguration returns whether the following gvk slice contains a UpgradeConfiguration object +func GroupVersionKindsHasUpgradeConfiguration(gvks ...schema.GroupVersionKind) bool { + return GroupVersionKindsHasKind(gvks, constants.UpgradeConfigurationKind) +} diff --git a/cmd/kubeadm/app/util/marshal_test.go b/cmd/kubeadm/app/util/marshal_test.go index ee3aac68aac..a477a4b6ae0 100644 --- a/cmd/kubeadm/app/util/marshal_test.go +++ b/cmd/kubeadm/app/util/marshal_test.go @@ -431,3 +431,69 @@ func TestGroupVersionKindsHasResetConfiguration(t *testing.T) { }) } } + +func TestGroupVersionKindsHasClusterConfiguration(t *testing.T) { + tests := []struct { + name string + gvks []schema.GroupVersionKind + expected bool + }{ + { + name: "does not have ClusterConfiguraiton", + gvks: []schema.GroupVersionKind{ + {Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}, + }, + expected: false, + }, + { + name: "has ClusterConfiguraiton", + gvks: []schema.GroupVersionKind{ + {Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}, + {Group: "foo.k8s.io", Version: "v1", Kind: "ClusterConfiguration"}, + }, + expected: true, + }, + } + for _, rt := range tests { + t.Run(rt.name, func(t *testing.T) { + actual := GroupVersionKindsHasClusterConfiguration(rt.gvks...) + if rt.expected != actual { + t.Errorf("expected gvks to have a ClusterConfiguration: %t\n\tactual: %t\n", rt.expected, actual) + } + }) + } +} + +func TestGroupVersionKindsHasUpgradeConfiguration(t *testing.T) { + var tests = []struct { + name string + gvks []schema.GroupVersionKind + kind string + expected bool + }{ + { + name: "no UpgradeConfiguration found", + gvks: []schema.GroupVersionKind{ + {Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}, + }, + expected: false, + }, + { + name: "UpgradeConfiguration is found", + gvks: []schema.GroupVersionKind{ + {Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}, + {Group: "bar.k8s.io", Version: "v2", Kind: "UpgradeConfiguration"}, + }, + expected: true, + }, + } + + for _, rt := range tests { + t.Run(rt.name, func(t2 *testing.T) { + actual := GroupVersionKindsHasUpgradeConfiguration(rt.gvks...) + if rt.expected != actual { + t2.Errorf("expected gvks has UpgradeConfiguration: %t\n\tactual: %t\n", rt.expected, actual) + } + }) + } +}