in the new output API output.kubeadm.k8s.io/v1alpha3 modify the UpgradePlan structure to include a list of multiple available upgrades.

This commit is contained in:
carlory 2024-02-23 14:18:48 +08:00
parent a882a2bf50
commit ea69a5ea98
16 changed files with 628 additions and 418 deletions

View File

@ -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
}

View File

@ -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.

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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"

View File

@ -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.

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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 <old-version>:<node-count>. 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)
}

View File

@ -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))
}
})
}

View File

@ -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,
})

View File

@ -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,

View File

@ -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"

View File

@ -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)