From ea69a5ea987bffe5d8e00286a6691df550e3fc4e Mon Sep 17 00:00:00 2001 From: carlory Date: Fri, 23 Feb 2024 14:18:48 +0800 Subject: [PATCH] in the new output API output.kubeadm.k8s.io/v1alpha3 modify the UpgradePlan structure to include a list of multiple available upgrades. --- cmd/kubeadm/app/apis/output/fuzzer/fuzzer.go | 8 + cmd/kubeadm/app/apis/output/types.go | 16 +- .../app/apis/output/v1alpha2/conversion.go | 28 ++ .../v1alpha2/zz_generated.conversion.go | 10 +- .../app/apis/output/v1alpha3/conversion.go | 28 ++ cmd/kubeadm/app/apis/output/v1alpha3/doc.go | 2 + cmd/kubeadm/app/apis/output/v1alpha3/types.go | 15 +- .../v1alpha3/zz_generated.conversion.go | 44 +- .../output/v1alpha3/zz_generated.deepcopy.go | 40 +- .../app/apis/output/zz_generated.deepcopy.go | 45 +- cmd/kubeadm/app/cmd/upgrade/plan.go | 448 +++++++----------- cmd/kubeadm/app/cmd/upgrade/plan_test.go | 323 +++++++++---- cmd/kubeadm/app/componentconfigs/configset.go | 10 +- .../app/componentconfigs/fakeconfig_test.go | 8 +- cmd/kubeadm/app/constants/constants.go | 2 + cmd/kubeadm/app/util/output/output.go | 19 - 16 files changed, 628 insertions(+), 418 deletions(-) create mode 100644 cmd/kubeadm/app/apis/output/v1alpha2/conversion.go create mode 100644 cmd/kubeadm/app/apis/output/v1alpha3/conversion.go diff --git a/cmd/kubeadm/app/apis/output/fuzzer/fuzzer.go b/cmd/kubeadm/app/apis/output/fuzzer/fuzzer.go index bc02a9a6af3..433af82853e 100644 --- a/cmd/kubeadm/app/apis/output/fuzzer/fuzzer.go +++ b/cmd/kubeadm/app/apis/output/fuzzer/fuzzer.go @@ -33,6 +33,7 @@ import ( func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ fuzzBootstrapToken, + fuzzUpgradePlan, } } @@ -45,3 +46,10 @@ func fuzzBootstrapToken(obj *output.BootstrapToken, c fuzz.Continue) { obj.Usages = []string{"authentication", "signing"} obj.Groups = []string{constants.NodeBootstrapTokenAuthGroup} } + +// TODO: Remove this func when v1alpha2 is removed +func fuzzUpgradePlan(obj *output.UpgradePlan, c fuzz.Continue) { + // Pin the value to avoid round tripping the AvailableUpgrades field + // which is not present in the v1alpha2 version. + obj.AvailableUpgrades = nil +} diff --git a/cmd/kubeadm/app/apis/output/types.go b/cmd/kubeadm/app/apis/output/types.go index 4f4fa33d845..2847694de31 100644 --- a/cmd/kubeadm/app/apis/output/types.go +++ b/cmd/kubeadm/app/apis/output/types.go @@ -73,14 +73,28 @@ type ComponentConfigVersionState struct { // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// AvailableUpgrade represents information for a single available upgrade. +type AvailableUpgrade struct { + metav1.TypeMeta + + Description string + + Components []ComponentUpgradePlan +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + // UpgradePlan represents information about upgrade plan for the output // produced by 'kubeadm upgrade plan' type UpgradePlan struct { metav1.TypeMeta - Components []ComponentUpgradePlan + AvailableUpgrades []AvailableUpgrade ConfigVersions []ComponentConfigVersionState + + // TODO: Remove this field when v1alpha2 is removed + Components []ComponentUpgradePlan } // Certificate represents information for a certificate or a certificate authority when using the check-expiration command. diff --git a/cmd/kubeadm/app/apis/output/v1alpha2/conversion.go b/cmd/kubeadm/app/apis/output/v1alpha2/conversion.go new file mode 100644 index 00000000000..464fb243535 --- /dev/null +++ b/cmd/kubeadm/app/apis/output/v1alpha2/conversion.go @@ -0,0 +1,28 @@ +/* +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 v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/conversion" + + "k8s.io/kubernetes/cmd/kubeadm/app/apis/output" +) + +// Convert_output_UpgradePlan_To_v1alpha2_UpgradePlan converts a private UpgradePlan to public UpgradePlan. +func Convert_output_UpgradePlan_To_v1alpha2_UpgradePlan(in *output.UpgradePlan, out *UpgradePlan, s conversion.Scope) error { + return autoConvert_output_UpgradePlan_To_v1alpha2_UpgradePlan(in, out, s) +} diff --git a/cmd/kubeadm/app/apis/output/v1alpha2/zz_generated.conversion.go b/cmd/kubeadm/app/apis/output/v1alpha2/zz_generated.conversion.go index 5893ba6c309..201661f39e1 100644 --- a/cmd/kubeadm/app/apis/output/v1alpha2/zz_generated.conversion.go +++ b/cmd/kubeadm/app/apis/output/v1alpha2/zz_generated.conversion.go @@ -81,7 +81,7 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*output.UpgradePlan)(nil), (*UpgradePlan)(nil), func(a, b interface{}, scope conversion.Scope) error { + if err := s.AddConversionFunc((*output.UpgradePlan)(nil), (*UpgradePlan)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_output_UpgradePlan_To_v1alpha2_UpgradePlan(a.(*output.UpgradePlan), b.(*UpgradePlan), scope) }); err != nil { return err @@ -191,12 +191,8 @@ func Convert_v1alpha2_UpgradePlan_To_output_UpgradePlan(in *UpgradePlan, out *ou } func autoConvert_output_UpgradePlan_To_v1alpha2_UpgradePlan(in *output.UpgradePlan, out *UpgradePlan, s conversion.Scope) error { - out.Components = *(*[]ComponentUpgradePlan)(unsafe.Pointer(&in.Components)) + // WARNING: in.AvailableUpgrades requires manual conversion: does not exist in peer-type out.ConfigVersions = *(*[]ComponentConfigVersionState)(unsafe.Pointer(&in.ConfigVersions)) + out.Components = *(*[]ComponentUpgradePlan)(unsafe.Pointer(&in.Components)) return nil } - -// Convert_output_UpgradePlan_To_v1alpha2_UpgradePlan is an autogenerated conversion function. -func Convert_output_UpgradePlan_To_v1alpha2_UpgradePlan(in *output.UpgradePlan, out *UpgradePlan, s conversion.Scope) error { - return autoConvert_output_UpgradePlan_To_v1alpha2_UpgradePlan(in, out, s) -} diff --git a/cmd/kubeadm/app/apis/output/v1alpha3/conversion.go b/cmd/kubeadm/app/apis/output/v1alpha3/conversion.go new file mode 100644 index 00000000000..9e6c8b95701 --- /dev/null +++ b/cmd/kubeadm/app/apis/output/v1alpha3/conversion.go @@ -0,0 +1,28 @@ +/* +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 v1alpha3 + +import ( + "k8s.io/apimachinery/pkg/conversion" + + "k8s.io/kubernetes/cmd/kubeadm/app/apis/output" +) + +// Convert_output_UpgradePlan_To_v1alpha3_UpgradePlan converts a private UpgradePlan to public UpgradePlan. +func Convert_output_UpgradePlan_To_v1alpha3_UpgradePlan(in *output.UpgradePlan, out *UpgradePlan, s conversion.Scope) error { + return autoConvert_output_UpgradePlan_To_v1alpha3_UpgradePlan(in, out, s) +} diff --git a/cmd/kubeadm/app/apis/output/v1alpha3/doc.go b/cmd/kubeadm/app/apis/output/v1alpha3/doc.go index 73e66ba4f6f..e25776c6ecc 100644 --- a/cmd/kubeadm/app/apis/output/v1alpha3/doc.go +++ b/cmd/kubeadm/app/apis/output/v1alpha3/doc.go @@ -27,4 +27,6 @@ limitations under the License. // Changes since v1alpha2: // - Added support for outputting certificate expiration information for "kubeadm certs check-expiration" // with the CertificateExpirationInfo structure. +// - Introduce a (breaking) change to the UpgradePlan structure used by "kubeadm upgrade plan". +// UpgradePlan now contains a list of AvailableUpgrade structures. package v1alpha3 // import "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha3" diff --git a/cmd/kubeadm/app/apis/output/v1alpha3/types.go b/cmd/kubeadm/app/apis/output/v1alpha3/types.go index 0311de1e95c..103cc2efd21 100644 --- a/cmd/kubeadm/app/apis/output/v1alpha3/types.go +++ b/cmd/kubeadm/app/apis/output/v1alpha3/types.go @@ -73,14 +73,25 @@ type ComponentConfigVersionState struct { // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// AvailableUpgrade represents information for a single available upgrade. +type AvailableUpgrade struct { + metav1.TypeMeta + + Description string `json:"description"` + + Components []ComponentUpgradePlan `json:"components"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + // UpgradePlan represents information about upgrade plan for the output // produced by 'kubeadm upgrade plan' type UpgradePlan struct { metav1.TypeMeta - Components []ComponentUpgradePlan `json:"components"` + AvailableUpgrades []AvailableUpgrade `json:"availableUpgrades,omitempty"` - ConfigVersions []ComponentConfigVersionState `json:"configVersions"` + ConfigVersions []ComponentConfigVersionState `json:"configVersions,omitempty"` } // Certificate represents information for a certificate or a certificate authority when using the check-expiration command. diff --git a/cmd/kubeadm/app/apis/output/v1alpha3/zz_generated.conversion.go b/cmd/kubeadm/app/apis/output/v1alpha3/zz_generated.conversion.go index 6c4915c74ba..f849ed2b8ad 100644 --- a/cmd/kubeadm/app/apis/output/v1alpha3/zz_generated.conversion.go +++ b/cmd/kubeadm/app/apis/output/v1alpha3/zz_generated.conversion.go @@ -36,6 +36,16 @@ func init() { // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*AvailableUpgrade)(nil), (*output.AvailableUpgrade)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha3_AvailableUpgrade_To_output_AvailableUpgrade(a.(*AvailableUpgrade), b.(*output.AvailableUpgrade), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*output.AvailableUpgrade)(nil), (*AvailableUpgrade)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_output_AvailableUpgrade_To_v1alpha3_AvailableUpgrade(a.(*output.AvailableUpgrade), b.(*AvailableUpgrade), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*BootstrapToken)(nil), (*output.BootstrapToken)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha3_BootstrapToken_To_output_BootstrapToken(a.(*BootstrapToken), b.(*output.BootstrapToken), scope) }); err != nil { @@ -101,7 +111,7 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*output.UpgradePlan)(nil), (*UpgradePlan)(nil), func(a, b interface{}, scope conversion.Scope) error { + if err := s.AddConversionFunc((*output.UpgradePlan)(nil), (*UpgradePlan)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_output_UpgradePlan_To_v1alpha3_UpgradePlan(a.(*output.UpgradePlan), b.(*UpgradePlan), scope) }); err != nil { return err @@ -109,6 +119,28 @@ func RegisterConversions(s *runtime.Scheme) error { return nil } +func autoConvert_v1alpha3_AvailableUpgrade_To_output_AvailableUpgrade(in *AvailableUpgrade, out *output.AvailableUpgrade, s conversion.Scope) error { + out.Description = in.Description + out.Components = *(*[]output.ComponentUpgradePlan)(unsafe.Pointer(&in.Components)) + return nil +} + +// Convert_v1alpha3_AvailableUpgrade_To_output_AvailableUpgrade is an autogenerated conversion function. +func Convert_v1alpha3_AvailableUpgrade_To_output_AvailableUpgrade(in *AvailableUpgrade, out *output.AvailableUpgrade, s conversion.Scope) error { + return autoConvert_v1alpha3_AvailableUpgrade_To_output_AvailableUpgrade(in, out, s) +} + +func autoConvert_output_AvailableUpgrade_To_v1alpha3_AvailableUpgrade(in *output.AvailableUpgrade, out *AvailableUpgrade, s conversion.Scope) error { + out.Description = in.Description + out.Components = *(*[]ComponentUpgradePlan)(unsafe.Pointer(&in.Components)) + return nil +} + +// Convert_output_AvailableUpgrade_To_v1alpha3_AvailableUpgrade is an autogenerated conversion function. +func Convert_output_AvailableUpgrade_To_v1alpha3_AvailableUpgrade(in *output.AvailableUpgrade, out *AvailableUpgrade, s conversion.Scope) error { + return autoConvert_output_AvailableUpgrade_To_v1alpha3_AvailableUpgrade(in, out, s) +} + func autoConvert_v1alpha3_BootstrapToken_To_output_BootstrapToken(in *BootstrapToken, out *output.BootstrapToken, s conversion.Scope) error { out.BootstrapToken = in.BootstrapToken return nil @@ -252,7 +284,7 @@ func Convert_output_Images_To_v1alpha3_Images(in *output.Images, out *Images, s } func autoConvert_v1alpha3_UpgradePlan_To_output_UpgradePlan(in *UpgradePlan, out *output.UpgradePlan, s conversion.Scope) error { - out.Components = *(*[]output.ComponentUpgradePlan)(unsafe.Pointer(&in.Components)) + out.AvailableUpgrades = *(*[]output.AvailableUpgrade)(unsafe.Pointer(&in.AvailableUpgrades)) out.ConfigVersions = *(*[]output.ComponentConfigVersionState)(unsafe.Pointer(&in.ConfigVersions)) return nil } @@ -263,12 +295,8 @@ func Convert_v1alpha3_UpgradePlan_To_output_UpgradePlan(in *UpgradePlan, out *ou } func autoConvert_output_UpgradePlan_To_v1alpha3_UpgradePlan(in *output.UpgradePlan, out *UpgradePlan, s conversion.Scope) error { - out.Components = *(*[]ComponentUpgradePlan)(unsafe.Pointer(&in.Components)) + out.AvailableUpgrades = *(*[]AvailableUpgrade)(unsafe.Pointer(&in.AvailableUpgrades)) out.ConfigVersions = *(*[]ComponentConfigVersionState)(unsafe.Pointer(&in.ConfigVersions)) + // WARNING: in.Components requires manual conversion: does not exist in peer-type return nil } - -// Convert_output_UpgradePlan_To_v1alpha3_UpgradePlan is an autogenerated conversion function. -func Convert_output_UpgradePlan_To_v1alpha3_UpgradePlan(in *output.UpgradePlan, out *UpgradePlan, s conversion.Scope) error { - return autoConvert_output_UpgradePlan_To_v1alpha3_UpgradePlan(in, out, s) -} diff --git a/cmd/kubeadm/app/apis/output/v1alpha3/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/output/v1alpha3/zz_generated.deepcopy.go index f8a93667bb6..770736c5e66 100644 --- a/cmd/kubeadm/app/apis/output/v1alpha3/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/output/v1alpha3/zz_generated.deepcopy.go @@ -25,6 +25,36 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AvailableUpgrade) DeepCopyInto(out *AvailableUpgrade) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Components != nil { + in, out := &in.Components, &out.Components + *out = make([]ComponentUpgradePlan, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AvailableUpgrade. +func (in *AvailableUpgrade) DeepCopy() *AvailableUpgrade { + if in == nil { + return nil + } + out := new(AvailableUpgrade) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AvailableUpgrade) 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 *BootstrapToken) DeepCopyInto(out *BootstrapToken) { *out = *in @@ -182,10 +212,12 @@ func (in *Images) DeepCopyObject() runtime.Object { func (in *UpgradePlan) DeepCopyInto(out *UpgradePlan) { *out = *in out.TypeMeta = in.TypeMeta - if in.Components != nil { - in, out := &in.Components, &out.Components - *out = make([]ComponentUpgradePlan, len(*in)) - copy(*out, *in) + if in.AvailableUpgrades != nil { + in, out := &in.AvailableUpgrades, &out.AvailableUpgrades + *out = make([]AvailableUpgrade, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } if in.ConfigVersions != nil { in, out := &in.ConfigVersions, &out.ConfigVersions diff --git a/cmd/kubeadm/app/apis/output/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/output/zz_generated.deepcopy.go index 098e52697ef..59d191243cc 100644 --- a/cmd/kubeadm/app/apis/output/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/output/zz_generated.deepcopy.go @@ -25,6 +25,36 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AvailableUpgrade) DeepCopyInto(out *AvailableUpgrade) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Components != nil { + in, out := &in.Components, &out.Components + *out = make([]ComponentUpgradePlan, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AvailableUpgrade. +func (in *AvailableUpgrade) DeepCopy() *AvailableUpgrade { + if in == nil { + return nil + } + out := new(AvailableUpgrade) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AvailableUpgrade) 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 *BootstrapToken) DeepCopyInto(out *BootstrapToken) { *out = *in @@ -182,16 +212,23 @@ func (in *Images) DeepCopyObject() runtime.Object { func (in *UpgradePlan) DeepCopyInto(out *UpgradePlan) { *out = *in out.TypeMeta = in.TypeMeta - if in.Components != nil { - in, out := &in.Components, &out.Components - *out = make([]ComponentUpgradePlan, len(*in)) - copy(*out, *in) + if in.AvailableUpgrades != nil { + in, out := &in.AvailableUpgrades, &out.AvailableUpgrades + *out = make([]AvailableUpgrade, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } if in.ConfigVersions != nil { in, out := &in.ConfigVersions, &out.ConfigVersions *out = make([]ComponentConfigVersionState, len(*in)) copy(*out, *in) } + if in.Components != nil { + in, out := &in.Components, &out.Components + *out = make([]ComponentUpgradePlan, len(*in)) + copy(*out, *in) + } return } diff --git a/cmd/kubeadm/app/cmd/upgrade/plan.go b/cmd/kubeadm/app/cmd/upgrade/plan.go index c655c3ab499..ef1bd190750 100644 --- a/cmd/kubeadm/app/cmd/upgrade/plan.go +++ b/cmd/kubeadm/app/cmd/upgrade/plan.go @@ -31,11 +31,10 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/version" "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/cli-runtime/pkg/printers" "k8s.io/klog/v2" outputapischeme "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/scheme" - outputapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha2" + outputapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha3" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs" "k8s.io/kubernetes/cmd/kubeadm/app/constants" @@ -59,7 +58,7 @@ func newCmdPlan(apf *applyPlanFlags) *cobra.Command { applyPlanFlags: apf, } - outputFlags := newUpgradePlanPrintFlags(output.TextOutput) + outputFlags := output.NewOutputFlags(&upgradePlanTextPrintFlags{}).WithTypeSetter(outputapischeme.Scheme).WithDefaultOutput(output.TextOutput) cmd := &cobra.Command{ Use: "plan [version] [flags]", @@ -82,174 +81,15 @@ func newCmdPlan(apf *applyPlanFlags) *cobra.Command { return cmd } -// newComponentUpgradePlan helper creates outputapiv1alpha2.ComponentUpgradePlan object -func newComponentUpgradePlan(name, currentVersion, newVersion string) outputapiv1alpha2.ComponentUpgradePlan { - return outputapiv1alpha2.ComponentUpgradePlan{ +// newComponentUpgradePlan helper creates outputapiv1alpha3.ComponentUpgradePlan object +func newComponentUpgradePlan(name, currentVersion, newVersion string) outputapiv1alpha3.ComponentUpgradePlan { + return outputapiv1alpha3.ComponentUpgradePlan{ Name: name, CurrentVersion: currentVersion, NewVersion: newVersion, } } -// upgradePlanPrintFlags defines a printer flag structure for the -// upgrade plan kubeadm command and provides a method -// of retrieving a known printer based on flag values provided. -type upgradePlanPrintFlags struct { - // JSONYamlPrintFlags provides default flags necessary for json/yaml printing - JSONYamlPrintFlags *upgradePlanJSONYamlPrintFlags - // TextPrintFlags provides default flags necessary for text printing - TextPrintFlags *upgradePlanTextPrintFlags - // TypeSetterPrinter is an implementation of ResourcePrinter that wraps another printer with types set on the objects - TypeSetterPrinter *printers.TypeSetterPrinter - // OutputFormat contains currently set output format - OutputFormat string -} - -func newUpgradePlanPrintFlags(outputFormat string) *upgradePlanPrintFlags { - return &upgradePlanPrintFlags{ - JSONYamlPrintFlags: &upgradePlanJSONYamlPrintFlags{}, - TextPrintFlags: &upgradePlanTextPrintFlags{}, - TypeSetterPrinter: printers.NewTypeSetter(outputapischeme.Scheme), - OutputFormat: strings.ToLower(outputFormat), - } -} - -// AllowedFormats returns a list of allowed output formats -func (pf *upgradePlanPrintFlags) AllowedFormats() []string { - ret := pf.TextPrintFlags.AllowedFormats() - return append(ret, pf.JSONYamlPrintFlags.AllowedFormats()...) -} - -// AddFlags receives a *cobra.Command reference and binds -// flags related to Kubeadm printing to it -func (pf *upgradePlanPrintFlags) AddFlags(cmd *cobra.Command) { - pf.TextPrintFlags.AddFlags(cmd) - pf.JSONYamlPrintFlags.AddFlags(cmd) - // TODO: once we are confident the feature is graduated we should remove the EXPERIMENTAL text below: - // https://github.com/kubernetes/kubeadm/issues/494 - cmd.Flags().StringVarP(&pf.OutputFormat, "output", "o", pf.OutputFormat, fmt.Sprintf("EXPERIMENTAL: Output format. One of: %s.", strings.Join(pf.AllowedFormats(), "|"))) -} - -// ToPrinter receives an outputFormat and returns a printer capable of -// handling format printing. -// Returns error if the specified outputFormat does not match supported formats. -func (pf *upgradePlanPrintFlags) ToPrinter() (output.Printer, error) { - switch pf.OutputFormat { - case output.TextOutput: - return pf.TextPrintFlags.ToPrinter(pf.OutputFormat) - case output.JSONOutput: - return newUpgradePlanJSONYAMLPrinter(pf.TypeSetterPrinter.WrapToPrinter(pf.JSONYamlPrintFlags.ToPrinter(output.JSONOutput))) - case output.YAMLOutput: - return newUpgradePlanJSONYAMLPrinter(pf.TypeSetterPrinter.WrapToPrinter(pf.JSONYamlPrintFlags.ToPrinter(output.YAMLOutput))) - default: - return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &pf.OutputFormat, AllowedFormats: pf.AllowedFormats()} - } -} - -type upgradePlanJSONYamlPrintFlags struct { - genericclioptions.JSONYamlPrintFlags -} - -// AllowedFormats returns a list of allowed output formats -func (pf *upgradePlanJSONYamlPrintFlags) AllowedFormats() []string { - return []string{output.JSONOutput, output.YAMLOutput} -} - -// upgradePlanJSONYAMLPrinter prints upgrade plan in a JSON or YAML format -type upgradePlanJSONYAMLPrinter struct { - output.ResourcePrinterWrapper - Components []outputapiv1alpha2.ComponentUpgradePlan - ConfigVersions []outputapiv1alpha2.ComponentConfigVersionState -} - -// newUpgradePlanJSONYAMLPrinter creates a new upgradePlanJSONYAMLPrinter object -func newUpgradePlanJSONYAMLPrinter(resourcePrinter printers.ResourcePrinter, err error) (output.Printer, error) { - if err != nil { - return nil, err - } - return &upgradePlanJSONYAMLPrinter{ResourcePrinterWrapper: output.ResourcePrinterWrapper{Printer: resourcePrinter}}, nil -} - -// PrintObj is an implementation of ResourcePrinter.PrintObj that adds object to the buffer -func (p *upgradePlanJSONYAMLPrinter) PrintObj(obj runtime.Object, writer io.Writer) error { - item, ok := obj.(*outputapiv1alpha2.ComponentUpgradePlan) - if !ok { - return errors.Errorf("expected ComponentUpgradePlan, but got %+v", obj) - } - p.Components = append(p.Components, *item) - return nil -} - -// Flush writes any buffered data once last object is added -func (p *upgradePlanJSONYAMLPrinter) Flush(writer io.Writer, last bool) { - if !last { - return - } - if len(p.Components) == 0 && len(p.ConfigVersions) == 0 { - return - } - plan := &outputapiv1alpha2.UpgradePlan{Components: p.Components, ConfigVersions: p.ConfigVersions} - if err := p.Printer.PrintObj(plan, writer); err != nil { - fmt.Fprintf(os.Stderr, "could not flush output buffer: %v\n", err) - } - p.Components = p.Components[:0] -} - -// Close does nothing. -func (p *upgradePlanJSONYAMLPrinter) Close(writer io.Writer) {} - -// upgradePlanTextPrinter prints upgrade plan in a text form -type upgradePlanTextPrinter struct { - output.TextPrinter - columns []string - tabwriter *tabwriter.Writer -} - -// Flush writes any buffered data -func (p *upgradePlanTextPrinter) Flush(writer io.Writer, last bool) { - if p.tabwriter != nil { - p.tabwriter.Flush() - p.tabwriter = nil - p.Fprintln(writer, "") - } -} - -// PrintObj is an implementation of ResourcePrinter.PrintObj for upgrade plan plain text output -func (p *upgradePlanTextPrinter) PrintObj(obj runtime.Object, writer io.Writer) error { - if p.tabwriter == nil { - p.tabwriter = tabwriter.NewWriter(writer, 10, 4, 3, ' ', 0) - // Print header - fmt.Fprintln(p.tabwriter, strings.Join(p.columns, "\t")) - } - - item, ok := obj.(*outputapiv1alpha2.ComponentUpgradePlan) - if !ok { - return errors.Errorf("expected ComponentUpgradePlan, but got %+v", obj) - } - - // Print item - fmt.Fprintf(p.tabwriter, "%s\t%s\t%s\n", item.Name, item.CurrentVersion, item.NewVersion) - return nil -} - -// upgradePlanTextPrintFlags provides flags necessary for printing upgrade plan in a text form -type upgradePlanTextPrintFlags struct{} - -func (pf *upgradePlanTextPrintFlags) AddFlags(cmd *cobra.Command) {} - -// AllowedFormats returns a list of allowed output formats -func (pf *upgradePlanTextPrintFlags) AllowedFormats() []string { - return []string{output.TextOutput} -} - -// ToPrinter returns a kubeadm printer for the text output format -func (pf *upgradePlanTextPrintFlags) ToPrinter(outputFormat string) (output.Printer, error) { - if outputFormat == output.TextOutput { - return &upgradePlanTextPrinter{columns: []string{"COMPONENT", "CURRENT", "TARGET"}}, nil - } - return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: []string{output.JSONOutput, output.YAMLOutput, output.TextOutput}} -} - // runPlan takes care of outputting available versions to upgrade to for the user func runPlan(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. @@ -284,34 +124,24 @@ func runPlan(flags *planFlags, args []string, printer output.Printer) error { return nil } - // A workaround to set the configVersionStates in the printer - if p, ok := printer.(*upgradePlanJSONYAMLPrinter); ok { - p.ConfigVersions = configVersionStates - } + // Generate and print the upgrade plan + plan := genUpgradePlan(availUpgrades, configVersionStates, isExternalEtcd) + return printer.PrintObj(plan, os.Stdout) +} - // Generate and print upgrade plans +// genUpgradePlan generates upgrade plan from available upgrades and component config version states +func genUpgradePlan(availUpgrades []upgrade.Upgrade, configVersions []outputapiv1alpha3.ComponentConfigVersionState, isExternalEtcd bool) *outputapiv1alpha3.UpgradePlan { + plan := &outputapiv1alpha3.UpgradePlan{ConfigVersions: configVersions} for _, up := range availUpgrades { - plan, unstableVersionFlag, err := genUpgradePlan(&up, isExternalEtcd) - if err != nil { - return err - } - - // Actually, this is needed for machine-readable output only. - // printUpgradePlan won't output the configVersionStates as it will simply print the same table several times - // in the human-readable output if it did so - plan.ConfigVersions = configVersionStates - - printUpgradePlan(&up, plan, unstableVersionFlag, isExternalEtcd, os.Stdout, printer) + au := genAvailableUpgrade(&up, isExternalEtcd) + plan.AvailableUpgrades = append(plan.AvailableUpgrades, au) } - - // Finally, print the component config state table - printComponentConfigVersionStates(configVersionStates, os.Stdout, printer) - return nil + return plan } // TODO There is currently no way to cleanly output upgrades that involve adding, removing, or changing components // https://github.com/kubernetes/kubeadm/issues/810 was created to track addressing this. -func appendDNSComponent(components []outputapiv1alpha2.ComponentUpgradePlan, up *upgrade.Upgrade, name string) []outputapiv1alpha2.ComponentUpgradePlan { +func appendDNSComponent(components []outputapiv1alpha3.ComponentUpgradePlan, up *upgrade.Upgrade, name string) []outputapiv1alpha3.ComponentUpgradePlan { beforeVersion := up.Before.DNSVersion afterVersion := up.After.DNSVersion @@ -321,23 +151,20 @@ func appendDNSComponent(components []outputapiv1alpha2.ComponentUpgradePlan, up return components } -// genUpgradePlan generates output-friendly upgrade plan out of upgrade.Upgrade structure -func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapiv1alpha2.UpgradePlan, string, error) { - newK8sVersion, err := version.ParseSemantic(up.After.KubeVersion) - if err != nil { - return nil, "", errors.Wrapf(err, "Unable to parse normalized version %q as a semantic version", up.After.KubeVersion) - } +// appendKubeadmComponent appends kubeadm component to the list of components +func appendKubeadmComponent(components []outputapiv1alpha3.ComponentUpgradePlan, up *upgrade.Upgrade, name string) []outputapiv1alpha3.ComponentUpgradePlan { + beforeVersion := up.Before.KubeadmVersion + afterVersion := up.After.KubeadmVersion - unstableVersionFlag := "" - if len(newK8sVersion.PreRelease()) != 0 { - if strings.HasPrefix(newK8sVersion.PreRelease(), "rc") { - unstableVersionFlag = " --allow-release-candidate-upgrades" - } else { - unstableVersionFlag = " --allow-experimental-upgrades" - } + if beforeVersion != "" || afterVersion != "" { + components = append(components, newComponentUpgradePlan(name, beforeVersion, afterVersion)) } + return components +} - components := []outputapiv1alpha2.ComponentUpgradePlan{} +// genAvailableUpgrade generates available upgrade from upgrade object and external etcd boolean +func genAvailableUpgrade(up *upgrade.Upgrade, isExternalEtcd bool) outputapiv1alpha3.AvailableUpgrade { + components := []outputapiv1alpha3.ComponentUpgradePlan{} if up.CanUpgradeKubelets() { // The map is of the form :. Here all the keys are put into a slice and sorted @@ -354,59 +181,13 @@ func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapiv1alpha components = append(components, newComponentUpgradePlan(constants.KubeProxy, up.Before.KubeVersion, up.After.KubeVersion)) components = appendDNSComponent(components, up, constants.CoreDNS) + components = appendKubeadmComponent(components, up, constants.Kubeadm) if !isExternalEtcd { components = append(components, newComponentUpgradePlan(constants.Etcd, up.Before.EtcdVersion, up.After.EtcdVersion)) } - return &outputapiv1alpha2.UpgradePlan{Components: components}, unstableVersionFlag, nil -} - -// printUpgradePlan prints a UX-friendly overview of what versions are available to upgrade to -func printUpgradePlan(up *upgrade.Upgrade, plan *outputapiv1alpha2.UpgradePlan, unstableVersionFlag string, isExternalEtcd bool, writer io.Writer, printer output.Printer) { - printHeader := true - printManualUpgradeHeader := true - for _, component := range plan.Components { - if isExternalEtcd && component.Name == constants.Etcd { - // Don't print etcd if it's external - continue - } else if component.Name == constants.Kubelet { - if printManualUpgradeHeader { - printer.Fprintln(writer, "Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':") - plan := newComponentUpgradePlan(component.Name, component.CurrentVersion, component.NewVersion) - printer.PrintObj(&plan, writer) - printManualUpgradeHeader = false - } else { - plan := newComponentUpgradePlan("", component.CurrentVersion, component.NewVersion) - printer.PrintObj(&plan, writer) - } - } else { - if printHeader { - // End of manual upgrades table - printer.Flush(writer, false) - printer.Fprintf(writer, "Upgrade to the latest %s:\n", up.Description) - printer.Fprintln(writer, "") - printHeader = false - } - plan := newComponentUpgradePlan(component.Name, component.CurrentVersion, component.NewVersion) - printer.PrintObj(&plan, writer) - } - } - - printer.Flush(writer, true) - - printer.Fprintln(writer, "You can now apply the upgrade by executing the following command:") - printer.Fprintln(writer, "") - printer.Fprintf(writer, "\tkubeadm upgrade apply %s%s\n", up.After.KubeVersion, unstableVersionFlag) - printer.Fprintln(writer, "") - - if up.Before.KubeadmVersion != up.After.KubeadmVersion { - printer.Fprintf(writer, "Note: Before you can perform this upgrade, you have to update kubeadm to %s.\n", up.After.KubeadmVersion) - printer.Fprintln(writer, "") - } - - printLineSeparator(writer, printer) - printer.Close(writer) + return outputapiv1alpha3.AvailableUpgrade{Description: up.Description, Components: components} } // sortedSliceFromStringIntMap returns a slice of the keys in the map sorted alphabetically @@ -419,6 +200,143 @@ func sortedSliceFromStringIntMap(strMap map[string]uint16) []string { return strSlice } +// upgradePlanTextPrintFlags provides flags necessary for printing upgrade plan in a text form +type upgradePlanTextPrintFlags struct{} + +// ToPrinter returns a kubeadm printer for the text output format +func (tpf *upgradePlanTextPrintFlags) ToPrinter(outputFormat string) (output.Printer, error) { + if outputFormat == output.TextOutput { + return &upgradePlanTextPrinter{}, nil + } + return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: []string{output.TextOutput}} +} + +// upgradePlanTextPrinter prints upgrade plan in a text form +type upgradePlanTextPrinter struct { + output.TextPrinter +} + +// PrintObj is an implementation of ResourcePrinter.PrintObj for upgrade plan plain text output +func (printer *upgradePlanTextPrinter) PrintObj(obj runtime.Object, writer io.Writer) error { + plan, ok := obj.(*outputapiv1alpha3.UpgradePlan) + if !ok { + return errors.Errorf("expected UpgradePlan, but got %+v", obj) + } + + for _, au := range plan.AvailableUpgrades { + if err := printer.printAvailableUpgrade(writer, &au); err != nil { + return err + } + } + printer.printComponentConfigVersionStates(writer, plan.ConfigVersions) + return nil +} + +// printUpgradePlan prints a UX-friendly overview of what versions are available to upgrade to +func (printer *upgradePlanTextPrinter) printAvailableUpgrade(writer io.Writer, au *outputapiv1alpha3.AvailableUpgrade) error { + var kubeVersion string + var beforeKubeadmVersion, afterKubeadmVersion string + for _, component := range au.Components { + if component.Name == constants.KubeAPIServer { + kubeVersion = component.NewVersion + } + if component.Name == constants.Kubeadm { + beforeKubeadmVersion = component.CurrentVersion + afterKubeadmVersion = component.NewVersion + } + } + + newK8sVersion, err := version.ParseSemantic(kubeVersion) + if err != nil { + return errors.Wrapf(err, "Unable to parse normalized version %q as a semantic version", kubeVersion) + } + + unstableVersionFlag := "" + if len(newK8sVersion.PreRelease()) != 0 { + if strings.HasPrefix(newK8sVersion.PreRelease(), "rc") { + unstableVersionFlag = " --allow-release-candidate-upgrades" + } else { + unstableVersionFlag = " --allow-experimental-upgrades" + } + } + + _, _ = printer.Fprintln(writer, "Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':") + tabw := tabwriter.NewWriter(writer, 10, 4, 3, ' ', 0) + _, _ = printer.Fprintln(tabw, strings.Join([]string{"COMPONENT", "CURRENT", "TARGET"}, "\t")) + for i, component := range au.Components { + if component.Name != constants.Kubelet { + continue + } + if i == 0 { + _, _ = printer.Fprintf(tabw, "%s\t%s\t%s\n", component.Name, component.CurrentVersion, component.NewVersion) + } else { + _, _ = printer.Fprintf(tabw, "%s\t%s\t%s\n", "", component.CurrentVersion, component.NewVersion) + } + } + _ = tabw.Flush() + + _, _ = printer.Fprintln(writer, "") + _, _ = printer.Fprintf(writer, "Upgrade to the latest %s:\n", au.Description) + _, _ = printer.Fprintln(writer, "") + _, _ = printer.Fprintln(tabw, strings.Join([]string{"COMPONENT", "CURRENT", "TARGET"}, "\t")) + for _, component := range au.Components { + if component.Name == constants.Kubelet || component.Name == constants.Kubeadm { + continue + } + _, _ = printer.Fprintf(tabw, "%s\t%s\t%s\n", component.Name, component.CurrentVersion, component.NewVersion) + } + _ = tabw.Flush() + + _, _ = printer.Fprintln(writer, "") + _, _ = printer.Fprintln(writer, "You can now apply the upgrade by executing the following command:") + _, _ = printer.Fprintln(writer, "") + _, _ = printer.Fprintf(writer, "\tkubeadm upgrade apply %s%s\n", kubeVersion, unstableVersionFlag) + _, _ = printer.Fprintln(writer, "") + + if beforeKubeadmVersion != afterKubeadmVersion { + _, _ = printer.Fprintf(writer, "Note: Before you can perform this upgrade, you have to update kubeadm to %s.\n", afterKubeadmVersion) + _, _ = printer.Fprintln(writer, "") + } + + printer.printLineSeparator(writer) + return nil +} + +// printComponentConfigVersionStates prints a UX-friendly overview of the current state of component configs +func (printer *upgradePlanTextPrinter) printComponentConfigVersionStates(w io.Writer, versionStates []outputapiv1alpha3.ComponentConfigVersionState) { + if len(versionStates) == 0 { + _, _ = printer.Fprintln(w, "No information available on component configs.") + return + } + + _, _ = printer.Fprintln(w, dedent.Dedent(` + The table below shows the current state of component configs as understood by this version of kubeadm. + Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or + resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually + upgrade to is denoted in the "PREFERRED VERSION" column. + `)) + + tabw := tabwriter.NewWriter(w, 10, 4, 3, ' ', 0) + _, _ = printer.Fprintln(tabw, strings.Join([]string{"API GROUP", "CURRENT VERSION", "PREFERRED VERSION", "MANUAL UPGRADE REQUIRED"}, "\t")) + + for _, state := range versionStates { + _, _ = printer.Fprintf(tabw, + "%s\t%s\t%s\t%s\n", + state.Group, + strOrDash(state.CurrentVersion), + strOrDash(state.PreferredVersion), + yesOrNo(state.ManualUpgradeRequired), + ) + } + + _ = tabw.Flush() + printer.printLineSeparator(w) +} + +func (printer *upgradePlanTextPrinter) printLineSeparator(w io.Writer) { + _, _ = printer.Fprintf(w, "_____________________________________________________________________\n\n") +} + func strOrDash(s string) string { if s != "" { return s @@ -432,37 +350,3 @@ func yesOrNo(b bool) string { } return "no" } - -func printLineSeparator(w io.Writer, printer output.Printer) { - printer.Fprintf(w, "_____________________________________________________________________\n\n") -} - -func printComponentConfigVersionStates(versionStates []outputapiv1alpha2.ComponentConfigVersionState, w io.Writer, printer output.Printer) { - if len(versionStates) == 0 { - printer.Fprintln(w, "No information available on component configs.") - return - } - - printer.Fprintln(w, dedent.Dedent(` - The table below shows the current state of component configs as understood by this version of kubeadm. - Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or - resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually - upgrade to is denoted in the "PREFERRED VERSION" column. - `)) - - tabw := tabwriter.NewWriter(w, 10, 4, 3, ' ', 0) - printer.Fprintln(tabw, "API GROUP\tCURRENT VERSION\tPREFERRED VERSION\tMANUAL UPGRADE REQUIRED") - - for _, state := range versionStates { - printer.Fprintf(tabw, - "%s\t%s\t%s\t%s\n", - state.Group, - strOrDash(state.CurrentVersion), - strOrDash(state.PreferredVersion), - yesOrNo(state.ManualUpgradeRequired), - ) - } - - tabw.Flush() - printLineSeparator(w, printer) -} diff --git a/cmd/kubeadm/app/cmd/upgrade/plan_test.go b/cmd/kubeadm/app/cmd/upgrade/plan_test.go index 82b489c8e88..ab30daa4828 100644 --- a/cmd/kubeadm/app/cmd/upgrade/plan_test.go +++ b/cmd/kubeadm/app/cmd/upgrade/plan_test.go @@ -21,6 +21,9 @@ import ( "reflect" "testing" + "k8s.io/apimachinery/pkg/util/diff" + 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/phases/upgrade" "k8s.io/kubernetes/cmd/kubeadm/app/util/output" ) @@ -67,10 +70,26 @@ func TestSortedSliceFromStringIntMap(t *testing.T) { } // TODO Think about modifying this test to be less verbose checking b/c it can be brittle. -func TestPrintAvailableUpgrades(t *testing.T) { +func TestPrintUpgradePlan(t *testing.T) { + versionStates := []outputapiv1alpha3.ComponentConfigVersionState{ + { + Group: "kubeproxy.config.k8s.io", + CurrentVersion: "v1alpha1", + PreferredVersion: "v1alpha1", + ManualUpgradeRequired: false, + }, + { + Group: "kubelet.config.k8s.io", + CurrentVersion: "v1beta1", + PreferredVersion: "v1beta1", + ManualUpgradeRequired: false, + }, + } + var tests = []struct { name string upgrades []upgrade.Upgrade + versionStates []outputapiv1alpha3.ComponentConfigVersionState buf *bytes.Buffer expectedBytes []byte externalEtcd bool @@ -97,6 +116,7 @@ func TestPrintAvailableUpgrades(t *testing.T) { }, }, }, + versionStates: versionStates, expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT CURRENT TARGET kubelet 1 x v1.18.1 v1.18.4 @@ -119,6 +139,17 @@ Note: Before you can perform this upgrade, you have to update kubeadm to v1.18.4 _____________________________________________________________________ + +The table below shows the current state of component configs as understood by this version of kubeadm. +Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or +resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually +upgrade to is denoted in the "PREFERRED VERSION" column. + +API GROUP CURRENT VERSION PREFERRED VERSION MANUAL UPGRADE REQUIRED +kubeproxy.config.k8s.io v1alpha1 v1alpha1 no +kubelet.config.k8s.io v1beta1 v1beta1 no +_____________________________________________________________________ + `), }, { @@ -143,6 +174,7 @@ _____________________________________________________________________ }, }, }, + versionStates: versionStates, expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT CURRENT TARGET kubelet 1 x v1.18.4 v1.19.0 @@ -165,6 +197,17 @@ Note: Before you can perform this upgrade, you have to update kubeadm to v1.19.0 _____________________________________________________________________ + +The table below shows the current state of component configs as understood by this version of kubeadm. +Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or +resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually +upgrade to is denoted in the "PREFERRED VERSION" column. + +API GROUP CURRENT VERSION PREFERRED VERSION MANUAL UPGRADE REQUIRED +kubeproxy.config.k8s.io v1alpha1 v1alpha1 no +kubelet.config.k8s.io v1beta1 v1beta1 no +_____________________________________________________________________ + `), }, { @@ -207,6 +250,7 @@ _____________________________________________________________________ }, }, }, + versionStates: versionStates, expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT CURRENT TARGET kubelet 1 x v1.18.3 v1.18.5 @@ -249,6 +293,17 @@ Note: Before you can perform this upgrade, you have to update kubeadm to v1.19.0 _____________________________________________________________________ + +The table below shows the current state of component configs as understood by this version of kubeadm. +Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or +resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually +upgrade to is denoted in the "PREFERRED VERSION" column. + +API GROUP CURRENT VERSION PREFERRED VERSION MANUAL UPGRADE REQUIRED +kubeproxy.config.k8s.io v1alpha1 v1alpha1 no +kubelet.config.k8s.io v1beta1 v1beta1 no +_____________________________________________________________________ + `), }, { @@ -273,6 +328,7 @@ _____________________________________________________________________ }, }, }, + versionStates: versionStates, expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT CURRENT TARGET kubelet 1 x v1.18.5 v1.19.0-beta.1 @@ -295,6 +351,17 @@ Note: Before you can perform this upgrade, you have to update kubeadm to v1.19.0 _____________________________________________________________________ + +The table below shows the current state of component configs as understood by this version of kubeadm. +Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or +resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually +upgrade to is denoted in the "PREFERRED VERSION" column. + +API GROUP CURRENT VERSION PREFERRED VERSION MANUAL UPGRADE REQUIRED +kubeproxy.config.k8s.io v1alpha1 v1alpha1 no +kubelet.config.k8s.io v1beta1 v1beta1 no +_____________________________________________________________________ + `), }, { @@ -319,6 +386,7 @@ _____________________________________________________________________ }, }, }, + versionStates: versionStates, expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT CURRENT TARGET kubelet 1 x v1.18.5 v1.19.0-rc.1 @@ -341,6 +409,17 @@ Note: Before you can perform this upgrade, you have to update kubeadm to v1.19.0 _____________________________________________________________________ + +The table below shows the current state of component configs as understood by this version of kubeadm. +Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or +resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually +upgrade to is denoted in the "PREFERRED VERSION" column. + +API GROUP CURRENT VERSION PREFERRED VERSION MANUAL UPGRADE REQUIRED +kubeproxy.config.k8s.io v1alpha1 v1alpha1 no +kubelet.config.k8s.io v1beta1 v1beta1 no +_____________________________________________________________________ + `), }, { @@ -366,6 +445,7 @@ _____________________________________________________________________ }, }, }, + versionStates: versionStates, expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT CURRENT TARGET kubelet 1 x v1.19.2 v1.19.3 @@ -389,6 +469,17 @@ Note: Before you can perform this upgrade, you have to update kubeadm to v1.19.3 _____________________________________________________________________ + +The table below shows the current state of component configs as understood by this version of kubeadm. +Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or +resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually +upgrade to is denoted in the "PREFERRED VERSION" column. + +API GROUP CURRENT VERSION PREFERRED VERSION MANUAL UPGRADE REQUIRED +kubeproxy.config.k8s.io v1alpha1 v1alpha1 no +kubelet.config.k8s.io v1beta1 v1beta1 no +_____________________________________________________________________ + `), }, @@ -414,7 +505,8 @@ _____________________________________________________________________ }, }, }, - externalEtcd: true, + versionStates: versionStates, + externalEtcd: true, expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT CURRENT TARGET kubelet 1 x v1.19.2 v1.19.3 @@ -436,30 +528,38 @@ Note: Before you can perform this upgrade, you have to update kubeadm to v1.19.3 _____________________________________________________________________ + +The table below shows the current state of component configs as understood by this version of kubeadm. +Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or +resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually +upgrade to is denoted in the "PREFERRED VERSION" column. + +API GROUP CURRENT VERSION PREFERRED VERSION MANUAL UPGRADE REQUIRED +kubeproxy.config.k8s.io v1alpha1 v1alpha1 no +kubelet.config.k8s.io v1beta1 v1beta1 no +_____________________________________________________________________ + `), }, } for _, rt := range tests { t.Run(rt.name, func(t *testing.T) { rt.buf = bytes.NewBufferString("") - outputFlags := newUpgradePlanPrintFlags(output.TextOutput) + outputFlags := output.NewOutputFlags(&upgradePlanTextPrintFlags{}).WithTypeSetter(outputapischeme.Scheme).WithDefaultOutput(output.TextOutput) printer, err := outputFlags.ToPrinter() if err != nil { t.Errorf("failed ToPrinter, err: %+v", err) } - // Generate and print upgrade plans - for _, up := range rt.upgrades { - plan, unstableVersionFlag, err := genUpgradePlan(&up, rt.externalEtcd) - if err != nil { - t.Errorf("failed genUpgradePlan, err: %+v", err) - } - printUpgradePlan(&up, plan, unstableVersionFlag, rt.externalEtcd, rt.buf, printer) + plan := genUpgradePlan(rt.upgrades, rt.versionStates, rt.externalEtcd) + if err := printer.PrintObj(plan, rt.buf); err != nil { + t.Errorf("unexpected error when print object: %v", err) } + actualBytes := rt.buf.Bytes() if !bytes.Equal(actualBytes, rt.expectedBytes) { t.Errorf( - "failed PrintAvailableUpgrades:\n\texpected: %q\n\n\tactual : %q", + "failed PrintUpgradePlan:\n\texpected: %q\n\n\tactual : %q", string(rt.expectedBytes), string(actualBytes), ) @@ -468,7 +568,7 @@ _____________________________________________________________________ } } -func TestPrintAvailableUpgradesStructured(t *testing.T) { +func TestPrintUpgradePlanStructured(t *testing.T) { upgrades := []upgrade.Upgrade{ { Description: "version in the v1.8 series", @@ -490,6 +590,21 @@ func TestPrintAvailableUpgradesStructured(t *testing.T) { }, } + versionStates := []outputapiv1alpha3.ComponentConfigVersionState{ + { + Group: "kubeproxy.config.k8s.io", + CurrentVersion: "v1alpha1", + PreferredVersion: "v1alpha1", + ManualUpgradeRequired: false, + }, + { + Group: "kubelet.config.k8s.io", + CurrentVersion: "v1beta1", + PreferredVersion: "v1beta1", + ManualUpgradeRequired: false, + }, + } + var tests = []struct { name string outputFormat string @@ -502,81 +617,117 @@ func TestPrintAvailableUpgradesStructured(t *testing.T) { outputFormat: "json", expected: `{ "kind": "UpgradePlan", - "apiVersion": "output.kubeadm.k8s.io/v1alpha2", - "components": [ + "apiVersion": "output.kubeadm.k8s.io/v1alpha3", + "availableUpgrades": [ { - "name": "kubelet", - "currentVersion": "1 x v1.8.1", - "newVersion": "v1.8.3" - }, - { - "name": "kube-apiserver", - "currentVersion": "v1.8.1", - "newVersion": "v1.8.3" - }, - { - "name": "kube-controller-manager", - "currentVersion": "v1.8.1", - "newVersion": "v1.8.3" - }, - { - "name": "kube-scheduler", - "currentVersion": "v1.8.1", - "newVersion": "v1.8.3" - }, - { - "name": "kube-proxy", - "currentVersion": "v1.8.1", - "newVersion": "v1.8.3" - }, - { - "name": "CoreDNS", - "currentVersion": "1.14.5", - "newVersion": "1.14.5" - }, - { - "name": "etcd", - "currentVersion": "3.0.17", - "newVersion": "3.0.17" + "description": "version in the v1.8 series", + "components": [ + { + "name": "kubelet", + "currentVersion": "1 x v1.8.1", + "newVersion": "v1.8.3" + }, + { + "name": "kube-apiserver", + "currentVersion": "v1.8.1", + "newVersion": "v1.8.3" + }, + { + "name": "kube-controller-manager", + "currentVersion": "v1.8.1", + "newVersion": "v1.8.3" + }, + { + "name": "kube-scheduler", + "currentVersion": "v1.8.1", + "newVersion": "v1.8.3" + }, + { + "name": "kube-proxy", + "currentVersion": "v1.8.1", + "newVersion": "v1.8.3" + }, + { + "name": "CoreDNS", + "currentVersion": "1.14.5", + "newVersion": "1.14.5" + }, + { + "name": "kubeadm", + "currentVersion": "v1.8.2", + "newVersion": "v1.8.3" + }, + { + "name": "etcd", + "currentVersion": "3.0.17", + "newVersion": "3.0.17" + } + ] } ], - "configVersions": null + "configVersions": [ + { + "group": "kubeproxy.config.k8s.io", + "currentVersion": "v1alpha1", + "preferredVersion": "v1alpha1", + "manualUpgradeRequired": false + }, + { + "group": "kubelet.config.k8s.io", + "currentVersion": "v1beta1", + "preferredVersion": "v1beta1", + "manualUpgradeRequired": false + } + ] } `, }, { name: "YAML output", outputFormat: "yaml", - expected: `apiVersion: output.kubeadm.k8s.io/v1alpha2 -components: -- currentVersion: 1 x v1.8.1 - name: kubelet - newVersion: v1.8.3 -- currentVersion: v1.8.1 - name: kube-apiserver - newVersion: v1.8.3 -- currentVersion: v1.8.1 - name: kube-controller-manager - newVersion: v1.8.3 -- currentVersion: v1.8.1 - name: kube-scheduler - newVersion: v1.8.3 -- currentVersion: v1.8.1 - name: kube-proxy - newVersion: v1.8.3 -- currentVersion: 1.14.5 - name: CoreDNS - newVersion: 1.14.5 -- currentVersion: 3.0.17 - name: etcd - newVersion: 3.0.17 -configVersions: null + expected: `apiVersion: output.kubeadm.k8s.io/v1alpha3 +availableUpgrades: +- components: + - currentVersion: 1 x v1.8.1 + name: kubelet + newVersion: v1.8.3 + - currentVersion: v1.8.1 + name: kube-apiserver + newVersion: v1.8.3 + - currentVersion: v1.8.1 + name: kube-controller-manager + newVersion: v1.8.3 + - currentVersion: v1.8.1 + name: kube-scheduler + newVersion: v1.8.3 + - currentVersion: v1.8.1 + name: kube-proxy + newVersion: v1.8.3 + - currentVersion: 1.14.5 + name: CoreDNS + newVersion: 1.14.5 + - currentVersion: v1.8.2 + name: kubeadm + newVersion: v1.8.3 + - currentVersion: 3.0.17 + name: etcd + newVersion: 3.0.17 + description: version in the v1.8 series +configVersions: +- currentVersion: v1alpha1 + group: kubeproxy.config.k8s.io + manualUpgradeRequired: false + preferredVersion: v1alpha1 +- currentVersion: v1beta1 + group: kubelet.config.k8s.io + manualUpgradeRequired: false + preferredVersion: v1beta1 kind: UpgradePlan `, }, { name: "Text output", - outputFormat: "Text", + outputFormat: "text", expected: `Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT CURRENT TARGET kubelet 1 x v1.8.1 v1.8.3 @@ -599,6 +750,17 @@ Note: Before you can perform this upgrade, you have to update kubeadm to v1.8.3. _____________________________________________________________________ + +The table below shows the current state of component configs as understood by this version of kubeadm. +Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or +resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually +upgrade to is denoted in the "PREFERRED VERSION" column. + +API GROUP CURRENT VERSION PREFERRED VERSION MANUAL UPGRADE REQUIRED +kubeproxy.config.k8s.io v1alpha1 v1alpha1 no +kubelet.config.k8s.io v1beta1 v1beta1 no +_____________________________________________________________________ + `, }, } @@ -606,24 +768,21 @@ _____________________________________________________________________ for _, rt := range tests { t.Run(rt.name, func(t *testing.T) { rt.buf = bytes.NewBufferString("") - outputFlags := newUpgradePlanPrintFlags(rt.outputFormat) + outputFlags := output.NewOutputFlags(&upgradePlanTextPrintFlags{}).WithTypeSetter(outputapischeme.Scheme).WithDefaultOutput(rt.outputFormat) printer, err := outputFlags.ToPrinter() if err != nil { t.Errorf("failed ToPrinter, err: %+v", err) } - // Generate and print upgrade plans - for _, up := range upgrades { - plan, unstableVersionFlag, err := genUpgradePlan(&up, rt.externalEtcd) - if err != nil { - t.Errorf("failed genUpgradePlan, err: %+v", err) - } - printUpgradePlan(&up, plan, unstableVersionFlag, rt.externalEtcd, rt.buf, printer) + plan := genUpgradePlan(upgrades, versionStates, false) + if err := printer.PrintObj(plan, rt.buf); err != nil { + t.Errorf("unexpected error when print object: %v", err) } actual := rt.buf.String() if actual != rt.expected { - t.Errorf("failed PrintAvailableUpgrades:\n\nexpected:\n%s\n\nactual:\n%s", rt.expected, actual) + + t.Errorf("failed PrintUpgradePlan:\n\nexpected:\n%s\n\nactual:\n%s\n\ndiff:\n%s", rt.expected, actual, diff.StringDiff(actual, rt.expected)) } }) } diff --git a/cmd/kubeadm/app/componentconfigs/configset.go b/cmd/kubeadm/app/componentconfigs/configset.go index a1ab2bfa972..c91024fe3d6 100644 --- a/cmd/kubeadm/app/componentconfigs/configset.go +++ b/cmd/kubeadm/app/componentconfigs/configset.go @@ -28,7 +28,7 @@ import ( "k8s.io/klog/v2" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - outputapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha2" + outputapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha3" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" "k8s.io/kubernetes/cmd/kubeadm/app/util/config/strict" @@ -289,7 +289,7 @@ func FetchFromClusterWithLocalOverwrites(clusterCfg *kubeadmapi.ClusterConfigura // GetVersionStates returns a slice of ComponentConfigVersionState structs // describing all supported component config groups that were identified on the cluster -func GetVersionStates(clusterCfg *kubeadmapi.ClusterConfiguration, client clientset.Interface) ([]outputapiv1alpha2.ComponentConfigVersionState, error) { +func GetVersionStates(clusterCfg *kubeadmapi.ClusterConfiguration, client clientset.Interface) ([]outputapiv1alpha3.ComponentConfigVersionState, error) { // We don't want to modify clusterCfg so we make a working deep copy of it. // Also, we don't want the defaulted component configs so we get rid of them. scratchClusterCfg := clusterCfg.DeepCopy() @@ -301,12 +301,12 @@ func GetVersionStates(clusterCfg *kubeadmapi.ClusterConfiguration, client client return nil, err } - results := []outputapiv1alpha2.ComponentConfigVersionState{} + results := []outputapiv1alpha3.ComponentConfigVersionState{} for _, handler := range known { group := handler.GroupVersion.Group if _, ok := scratchClusterCfg.ComponentConfigs[group]; ok { // Normally loaded component config. No manual upgrade required on behalf of users. - results = append(results, outputapiv1alpha2.ComponentConfigVersionState{ + results = append(results, outputapiv1alpha3.ComponentConfigVersionState{ Group: group, CurrentVersion: handler.GroupVersion.Version, // Currently kubeadm supports only one version per API PreferredVersion: handler.GroupVersion.Version, // group so we can get away with these being the same @@ -315,7 +315,7 @@ func GetVersionStates(clusterCfg *kubeadmapi.ClusterConfiguration, client client // This config was either not present (user did not install an addon) or the config was unsupported kubeadm // generated one and is therefore skipped so we can automatically re-generate it (no action required on // behalf of the user). - results = append(results, outputapiv1alpha2.ComponentConfigVersionState{ + results = append(results, outputapiv1alpha3.ComponentConfigVersionState{ Group: group, PreferredVersion: handler.GroupVersion.Version, }) diff --git a/cmd/kubeadm/app/componentconfigs/fakeconfig_test.go b/cmd/kubeadm/app/componentconfigs/fakeconfig_test.go index 36227bfd613..b41a279a069 100644 --- a/cmd/kubeadm/app/componentconfigs/fakeconfig_test.go +++ b/cmd/kubeadm/app/componentconfigs/fakeconfig_test.go @@ -35,7 +35,7 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3" - outputapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha2" + outputapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha3" "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" ) @@ -612,7 +612,7 @@ func TestFetchFromClusterWithLocalOverwrites(t *testing.T) { func TestGetVersionStates(t *testing.T) { fakeKnownContext(func() { - versionStateCurrent := outputapiv1alpha2.ComponentConfigVersionState{ + versionStateCurrent := outputapiv1alpha3.ComponentConfigVersionState{ Group: kubeadmapiv1.GroupName, CurrentVersion: currentClusterConfigVersion, PreferredVersion: currentClusterConfigVersion, @@ -622,7 +622,7 @@ func TestGetVersionStates(t *testing.T) { desc string obj runtime.Object expectedErr bool - expected outputapiv1alpha2.ComponentConfigVersionState + expected outputapiv1alpha3.ComponentConfigVersionState }{ { desc: "appropriate cluster object", @@ -642,7 +642,7 @@ func TestGetVersionStates(t *testing.T) { { desc: "old signed config", obj: testClusterConfigMap(oldFooClusterConfig, true), - expected: outputapiv1alpha2.ComponentConfigVersionState{ + expected: outputapiv1alpha3.ComponentConfigVersionState{ Group: kubeadmapiv1.GroupName, CurrentVersion: "", // The config is treated as if it's missing PreferredVersion: currentClusterConfigVersion, diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index 947689b2ea6..3dc9cc6dc01 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -340,6 +340,8 @@ const ( CoreDNS = "CoreDNS" // Kubelet defines variable used internally when referring to the Kubelet Kubelet = "kubelet" + // Kubeadm defines variable used internally when referring to the kubeadm component + Kubeadm = "kubeadm" // KubeCertificatesVolumeName specifies the name for the Volume that is used for injecting certificates to control plane components (can be both a hostPath volume or a projected, all-in-one volume) KubeCertificatesVolumeName = "k8s-certs" diff --git a/cmd/kubeadm/app/util/output/output.go b/cmd/kubeadm/app/util/output/output.go index e662c943abe..db1aee23a26 100644 --- a/cmd/kubeadm/app/util/output/output.go +++ b/cmd/kubeadm/app/util/output/output.go @@ -144,9 +144,6 @@ type Printer interface { Fprintln(writer io.Writer, args ...interface{}) (n int, err error) Printf(format string, args ...interface{}) (n int, err error) Println(args ...interface{}) (n int, err error) - - Flush(writer io.Writer, last bool) - Close(writer io.Writer) } // TextPrinter implements Printer interface for generic text output @@ -179,14 +176,6 @@ func (tp *TextPrinter) Println(args ...interface{}) (n int, err error) { return fmt.Println(args...) } -// Flush writes any buffered data -func (tp *TextPrinter) Flush(writer io.Writer, last bool) { -} - -// Close flushes any buffered data and closes the printer -func (tp *TextPrinter) Close(writer io.Writer) { -} - // ResourcePrinterWrapper wraps ResourcePrinter and implements Printer interface type ResourcePrinterWrapper struct { Printer printers.ResourcePrinter @@ -200,14 +189,6 @@ func NewResourcePrinterWrapper(resourcePrinter printers.ResourcePrinter, err err return &ResourcePrinterWrapper{Printer: resourcePrinter}, nil } -// Flush writes any buffered data -func (rpw *ResourcePrinterWrapper) Flush(writer io.Writer, last bool) { -} - -// Close flushes any buffered data and closes the printer -func (rpw *ResourcePrinterWrapper) Close(writer io.Writer) { -} - // PrintObj is an implementation of ResourcePrinter.PrintObj that calls underlying printer API func (rpw *ResourcePrinterWrapper) PrintObj(obj runtime.Object, writer io.Writer) error { return rpw.Printer.PrintObj(obj, writer)