Merge pull request #108447 from pacoxu/kubeadm-json-upgrade-plan

`Kubeadm upgrade plan` support json/yaml output
This commit is contained in:
Kubernetes Prow Robot 2022-05-17 16:46:31 -07:00 committed by GitHub
commit 9169f16841
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 539 additions and 123 deletions

View File

@ -40,8 +40,12 @@ type Images struct {
Images []string Images []string
} }
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// ComponentUpgradePlan represents information about upgrade plan for one component // ComponentUpgradePlan represents information about upgrade plan for one component
type ComponentUpgradePlan struct { type ComponentUpgradePlan struct {
metav1.TypeMeta
Name string Name string
CurrentVersion string CurrentVersion string
NewVersion string NewVersion string

View File

@ -40,8 +40,12 @@ type Images struct {
Images []string `json:"images"` Images []string `json:"images"`
} }
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// ComponentUpgradePlan represents information about upgrade plan for one component // ComponentUpgradePlan represents information about upgrade plan for one component
type ComponentUpgradePlan struct { type ComponentUpgradePlan struct {
metav1.TypeMeta
Name string `json:"name"` Name string `json:"name"`
CurrentVersion string `json:"currentVersion"` CurrentVersion string `json:"currentVersion"`
NewVersion string `json:"newVersion"` NewVersion string `json:"newVersion"`

View File

@ -70,6 +70,7 @@ func (in *ComponentConfigVersionState) DeepCopy() *ComponentConfigVersionState {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentUpgradePlan) DeepCopyInto(out *ComponentUpgradePlan) { func (in *ComponentUpgradePlan) DeepCopyInto(out *ComponentUpgradePlan) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta
return return
} }
@ -83,6 +84,14 @@ func (in *ComponentUpgradePlan) DeepCopy() *ComponentUpgradePlan {
return out return out
} }
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ComponentUpgradePlan) 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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Images) DeepCopyInto(out *Images) { func (in *Images) DeepCopyInto(out *Images) {
*out = *in *out = *in

View File

@ -70,6 +70,7 @@ func (in *ComponentConfigVersionState) DeepCopy() *ComponentConfigVersionState {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentUpgradePlan) DeepCopyInto(out *ComponentUpgradePlan) { func (in *ComponentUpgradePlan) DeepCopyInto(out *ComponentUpgradePlan) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta
return return
} }
@ -83,6 +84,14 @@ func (in *ComponentUpgradePlan) DeepCopy() *ComponentUpgradePlan {
return out return out
} }
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ComponentUpgradePlan) 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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Images) DeepCopyInto(out *Images) { func (in *Images) DeepCopyInto(out *Images) {
*out = *in *out = *in

View File

@ -332,7 +332,7 @@ func getInternalCfg(cfgPath string, kubeconfigPath string, cfg kubeadmapiv1.Clus
if cfgPath == "" { if cfgPath == "" {
client, err := kubeconfigutil.ClientSetFromFile(kubeconfigPath) client, err := kubeconfigutil.ClientSetFromFile(kubeconfigPath)
if err == nil { if err == nil {
internalcfg, err := configutil.FetchInitConfigurationFromCluster(client, out, logPrefix, false, false) internalcfg, err := configutil.FetchInitConfigurationFromCluster(client, nil, logPrefix, false, false)
if err == nil { if err == nil {
fmt.Println() // add empty line to separate the FetchInitConfigurationFromCluster output from the command output fmt.Println() // add empty line to separate the FetchInitConfigurationFromCluster output from the command output
return internalcfg, nil return internalcfg, nil

View File

@ -371,7 +371,7 @@ func newCmdConfigImagesList(out io.Writer, mockK8sVersion *string) *cobra.Comman
printer, err := outputFlags.ToPrinter() printer, err := outputFlags.ToPrinter()
if err != nil { if err != nil {
return err return errors.Wrap(err, "could not construct output printer")
} }
imagesList, err := NewImagesList(cfgPath, externalcfg) imagesList, err := NewImagesList(cfgPath, externalcfg)
@ -424,7 +424,7 @@ func (itp *imageTextPrinter) PrintObj(obj runtime.Object, writer io.Writer) erro
// imageTextPrintFlags provides flags necessary for printing image in a text form. // imageTextPrintFlags provides flags necessary for printing image in a text form.
type imageTextPrintFlags struct{} type imageTextPrintFlags struct{}
// ToPrinter returns kubeadm printer for the text output format // ToPrinter returns a kubeadm printer for the text output format
func (ipf *imageTextPrintFlags) ToPrinter(outputFormat string) (output.Printer, error) { func (ipf *imageTextPrintFlags) ToPrinter(outputFormat string) (output.Printer, error) {
if outputFormat == output.TextOutput { if outputFormat == output.TextOutput {
return &imageTextPrinter{}, nil return &imageTextPrinter{}, nil

View File

@ -618,7 +618,7 @@ func fetchInitConfiguration(tlsBootstrapCfg *clientcmdapi.Config) (*kubeadmapi.I
} }
// Fetches the init configuration // Fetches the init configuration
initConfiguration, err := configutil.FetchInitConfigurationFromCluster(tlsClient, os.Stdout, "preflight", true, false) initConfiguration, err := configutil.FetchInitConfigurationFromCluster(tlsClient, nil, "preflight", true, false)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap") return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap")
} }

View File

@ -98,7 +98,7 @@ func newResetData(cmd *cobra.Command, options *resetOptions, in io.Reader, out i
client, err := getClientset(options.kubeconfigPath, false) client, err := getClientset(options.kubeconfigPath, false)
if err == nil { if err == nil {
klog.V(1).Infof("[reset] Loaded client set from kubeconfig file: %s", options.kubeconfigPath) klog.V(1).Infof("[reset] Loaded client set from kubeconfig file: %s", options.kubeconfigPath)
cfg, err = configutil.FetchInitConfigurationFromCluster(client, out, "reset", false, false) cfg, err = configutil.FetchInitConfigurationFromCluster(client, nil, "reset", false, false)
if err != nil { if err != nil {
klog.Warningf("[reset] Unable to fetch the kubeadm-config ConfigMap from cluster: %v", err) klog.Warningf("[reset] Unable to fetch the kubeadm-config ConfigMap from cluster: %v", err)
} }

View File

@ -169,7 +169,7 @@ func newCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
printer, err := outputFlags.ToPrinter() printer, err := outputFlags.ToPrinter()
if err != nil { if err != nil {
return err return errors.Wrap(err, "could not construct output printer")
} }
return RunListTokens(out, errW, client, printer) return RunListTokens(out, errW, client, printer)
@ -354,7 +354,7 @@ func (ttp *tokenTextPrinter) PrintObj(obj runtime.Object, writer io.Writer) erro
// tokenTextPrintFlags provides flags necessary for printing bootstrap token in a text form. // tokenTextPrintFlags provides flags necessary for printing bootstrap token in a text form.
type tokenTextPrintFlags struct{} type tokenTextPrintFlags struct{}
// ToPrinter returns kubeadm printer for the text output format // ToPrinter returns a kubeadm printer for the text output format
func (tpf *tokenTextPrintFlags) ToPrinter(outputFormat string) (output.Printer, error) { func (tpf *tokenTextPrintFlags) ToPrinter(outputFormat string) (output.Printer, error) {
if outputFormat == output.TextOutput { if outputFormat == output.TextOutput {
return &tokenTextPrinter{columns: []string{"TOKEN", "TTL", "EXPIRES", "USAGES", "DESCRIPTION", "EXTRA GROUPS"}}, nil return &tokenTextPrinter{columns: []string{"TOKEN", "TTL", "EXPIRES", "USAGES", "DESCRIPTION", "EXTRA GROUPS"}}, nil

View File

@ -37,6 +37,7 @@ import (
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
) )
// applyFlags holds the information about the flags that can be passed to apply // applyFlags holds the information about the flags that can be passed to apply
@ -103,7 +104,7 @@ func runApply(flags *applyFlags, args []string) error {
// Start with the basics, verify that the cluster is healthy and get the configuration from the cluster (using the ConfigMap) // Start with the basics, verify that the cluster is healthy and get the configuration from the cluster (using the ConfigMap)
klog.V(1).Infoln("[upgrade/apply] verifying health of cluster") klog.V(1).Infoln("[upgrade/apply] verifying health of cluster")
klog.V(1).Infoln("[upgrade/apply] retrieving configuration from cluster") klog.V(1).Infoln("[upgrade/apply] retrieving configuration from cluster")
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, flags.dryRun, true) client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, flags.dryRun, true, &output.TextPrinter{})
if err != nil { if err != nil {
return err return err
} }

View File

@ -47,6 +47,7 @@ import (
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
) )
// isKubeadmConfigPresent checks if a kubeadm config type is found in the provided document map // isKubeadmConfigPresent checks if a kubeadm config type is found in the provided document map
@ -63,14 +64,14 @@ func isKubeadmConfigPresent(docmap kubeadmapi.DocumentMap) bool {
// are loaded. This function allows the component configs to be loaded from a file that contains only them. If the file contains any kubeadm types // are loaded. This function allows the component configs to be loaded from a file that contains only them. If the file contains any kubeadm types
// in it (API group "kubeadm.kubernetes.io" present), then the supplied file is treaded as a legacy reconfiguration style "--config" use and the // in it (API group "kubeadm.kubernetes.io" present), then the supplied file is treaded as a legacy reconfiguration style "--config" use and the
// returned bool value is set to true (the only case to be done so). // returned bool value is set to true (the only case to be done so).
func loadConfig(cfgPath string, client clientset.Interface, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, bool, error) { func loadConfig(cfgPath string, client clientset.Interface, skipComponentConfigs bool, printer output.Printer) (*kubeadmapi.InitConfiguration, bool, error) {
// Used for info logs here // Used for info logs here
const logPrefix = "upgrade/config" const logPrefix = "upgrade/config"
// The usual case here is to not have a config file, but rather load the config from the cluster. // The usual case here is to not have a config file, but rather load the config from the cluster.
// This is probably 90% of the time. So we handle it first. // This is probably 90% of the time. So we handle it first.
if cfgPath == "" { if cfgPath == "" {
cfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, logPrefix, false, skipComponentConfigs) cfg, err := configutil.FetchInitConfigurationFromCluster(client, printer, logPrefix, false, skipComponentConfigs)
return cfg, false, err return cfg, false, err
} }
@ -98,7 +99,7 @@ func loadConfig(cfgPath string, client clientset.Interface, skipComponentConfigs
// If no kubeadm config types are present, we assume that there are manually upgraded component configs in the file. // If no kubeadm config types are present, we assume that there are manually upgraded component configs in the file.
// Hence, we load the kubeadm types from the cluster. // Hence, we load the kubeadm types from the cluster.
initCfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, logPrefix, false, true) initCfg, err := configutil.FetchInitConfigurationFromCluster(client, printer, logPrefix, false, true)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
@ -121,27 +122,27 @@ func loadConfig(cfgPath string, client clientset.Interface, skipComponentConfigs
} }
// enforceRequirements verifies that it's okay to upgrade and then returns the variables needed for the rest of the procedure // enforceRequirements verifies that it's okay to upgrade and then returns the variables needed for the rest of the procedure
func enforceRequirements(flags *applyPlanFlags, args []string, dryRun bool, upgradeApply bool) (clientset.Interface, upgrade.VersionGetter, *kubeadmapi.InitConfiguration, error) { func enforceRequirements(flags *applyPlanFlags, args []string, dryRun bool, upgradeApply bool, printer output.Printer) (clientset.Interface, upgrade.VersionGetter, *kubeadmapi.InitConfiguration, error) {
client, err := getClient(flags.kubeConfigPath, dryRun) client, err := getClient(flags.kubeConfigPath, dryRun)
if err != nil { if err != nil {
return nil, nil, nil, errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath) return nil, nil, nil, errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath)
} }
// Fetch the configuration from a file or ConfigMap and validate it // Fetch the configuration from a file or ConfigMap and validate it
fmt.Println("[upgrade/config] Making sure the configuration is correct:") printer.Printf("[upgrade/config] Making sure the configuration is correct:\n")
var newK8sVersion string var newK8sVersion string
cfg, legacyReconfigure, err := loadConfig(flags.cfgPath, client, !upgradeApply) cfg, legacyReconfigure, err := loadConfig(flags.cfgPath, client, !upgradeApply, printer)
if err != nil { if err != nil {
if apierrors.IsNotFound(err) { if apierrors.IsNotFound(err) {
fmt.Printf("[upgrade/config] In order to upgrade, a ConfigMap called %q in the %s namespace must exist.\n", constants.KubeadmConfigConfigMap, metav1.NamespaceSystem) printer.Printf("[upgrade/config] In order to upgrade, a ConfigMap called %q in the %s namespace must exist.\n", constants.KubeadmConfigConfigMap, metav1.NamespaceSystem)
fmt.Println("[upgrade/config] Without this information, 'kubeadm upgrade' won't know how to configure your upgraded cluster.") printer.Printf("[upgrade/config] Without this information, 'kubeadm upgrade' won't know how to configure your upgraded cluster.\n")
fmt.Println("") printer.Println()
fmt.Println("[upgrade/config] Next steps:") printer.Printf("[upgrade/config] Next steps:\n")
fmt.Printf("\t- OPTION 1: Run 'kubeadm config upload from-flags' and specify the same CLI arguments you passed to 'kubeadm init' when you created your control-plane.\n") printer.Printf("\t- OPTION 1: Run 'kubeadm config upload from-flags' and specify the same CLI arguments you passed to 'kubeadm init' when you created your control-plane.\n")
fmt.Printf("\t- OPTION 2: Run 'kubeadm config upload from-file' and specify the same config file you passed to 'kubeadm init' when you created your control-plane.\n") printer.Printf("\t- OPTION 2: Run 'kubeadm config upload from-file' and specify the same config file you passed to 'kubeadm init' when you created your control-plane.\n")
fmt.Printf("\t- OPTION 3: Pass a config file to 'kubeadm upgrade' using the --config flag.\n") printer.Printf("\t- OPTION 3: Pass a config file to 'kubeadm upgrade' using the --config flag.\n")
fmt.Println("") printer.Println()
err = errors.Errorf("the ConfigMap %q in the %s namespace used for getting configuration information was not found", constants.KubeadmConfigConfigMap, metav1.NamespaceSystem) err = errors.Errorf("the ConfigMap %q in the %s namespace used for getting configuration information was not found", constants.KubeadmConfigConfigMap, metav1.NamespaceSystem)
} }
return nil, nil, nil, errors.Wrap(err, "[upgrade/config] FATAL") return nil, nil, nil, errors.Wrap(err, "[upgrade/config] FATAL")
@ -161,7 +162,7 @@ func enforceRequirements(flags *applyPlanFlags, args []string, dryRun bool, upgr
// Ensure the user is root // Ensure the user is root
klog.V(1).Info("running preflight checks") klog.V(1).Info("running preflight checks")
if err := runPreflightChecks(client, ignorePreflightErrorsSet, &cfg.ClusterConfiguration); err != nil { if err := runPreflightChecks(client, ignorePreflightErrorsSet, &cfg.ClusterConfiguration, printer); err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
@ -202,14 +203,14 @@ func enforceRequirements(flags *applyPlanFlags, args []string, dryRun bool, upgr
// Check if feature gate flags used in the cluster are consistent with the set of features currently supported by kubeadm // Check if feature gate flags used in the cluster are consistent with the set of features currently supported by kubeadm
if msg := features.CheckDeprecatedFlags(&features.InitFeatureGates, cfg.FeatureGates); len(msg) > 0 { if msg := features.CheckDeprecatedFlags(&features.InitFeatureGates, cfg.FeatureGates); len(msg) > 0 {
for _, m := range msg { for _, m := range msg {
fmt.Printf("[upgrade/config] %s\n", m) printer.Printf("[upgrade/config] %s\n", m)
} }
return nil, nil, nil, errors.New("[upgrade/config] FATAL. Unable to upgrade a cluster using deprecated feature-gate flags. Please see the release notes") return nil, nil, nil, errors.New("[upgrade/config] FATAL. Unable to upgrade a cluster using deprecated feature-gate flags. Please see the release notes")
} }
// If the user told us to print this information out; do it! // If the user told us to print this information out; do it!
if flags.printConfig { if flags.printConfig {
printConfiguration(&cfg.ClusterConfiguration, os.Stdout) printConfiguration(&cfg.ClusterConfiguration, os.Stdout, printer)
} }
// Use a real version getter interface that queries the API server, the kubeadm client and the Kubernetes CI system for latest versions // Use a real version getter interface that queries the API server, the kubeadm client and the Kubernetes CI system for latest versions
@ -217,7 +218,7 @@ func enforceRequirements(flags *applyPlanFlags, args []string, dryRun bool, upgr
} }
// printConfiguration prints the external version of the API to yaml // printConfiguration prints the external version of the API to yaml
func printConfiguration(clustercfg *kubeadmapi.ClusterConfiguration, w io.Writer) { func printConfiguration(clustercfg *kubeadmapi.ClusterConfiguration, w io.Writer, printer output.Printer) {
// Short-circuit if cfg is nil, so we can safely get the value of the pointer below // Short-circuit if cfg is nil, so we can safely get the value of the pointer below
if clustercfg == nil { if clustercfg == nil {
return return
@ -225,18 +226,18 @@ func printConfiguration(clustercfg *kubeadmapi.ClusterConfiguration, w io.Writer
cfgYaml, err := configutil.MarshalKubeadmConfigObject(clustercfg) cfgYaml, err := configutil.MarshalKubeadmConfigObject(clustercfg)
if err == nil { if err == nil {
fmt.Fprintln(w, "[upgrade/config] Configuration used:") printer.Fprintln(w, "[upgrade/config] Configuration used:")
scanner := bufio.NewScanner(bytes.NewReader(cfgYaml)) scanner := bufio.NewScanner(bytes.NewReader(cfgYaml))
for scanner.Scan() { for scanner.Scan() {
fmt.Fprintf(w, "\t%s\n", scanner.Text()) printer.Fprintf(w, "\t%s\n", scanner.Text())
} }
} }
} }
// runPreflightChecks runs the root preflight check // runPreflightChecks runs the root preflight check
func runPreflightChecks(client clientset.Interface, ignorePreflightErrors sets.String, cfg *kubeadmapi.ClusterConfiguration) error { func runPreflightChecks(client clientset.Interface, ignorePreflightErrors sets.String, cfg *kubeadmapi.ClusterConfiguration, printer output.Printer) error {
fmt.Println("[preflight] Running pre-flight checks.") printer.Printf("[preflight] Running pre-flight checks.\n")
err := preflight.RunRootCheckOnly(ignorePreflightErrors) err := preflight.RunRootCheckOnly(ignorePreflightErrors)
if err != nil { if err != nil {
return err return err

View File

@ -23,6 +23,7 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3" kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
) )
func TestEnforceRequirements(t *testing.T) { func TestEnforceRequirements(t *testing.T) {
@ -54,7 +55,7 @@ func TestEnforceRequirements(t *testing.T) {
} }
for _, tt := range tcases { for _, tt := range tcases {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
_, _, _, err := enforceRequirements(&tt.flags, nil, tt.dryRun, false) _, _, _, err := enforceRequirements(&tt.flags, nil, tt.dryRun, false, &output.TextPrinter{})
if err == nil && tt.expectedErr { if err == nil && tt.expectedErr {
t.Error("Expected error, but got success") t.Error("Expected error, but got success")
@ -138,7 +139,7 @@ func TestPrintConfiguration(t *testing.T) {
for _, rt := range tests { for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) { t.Run(rt.name, func(t *testing.T) {
rt.buf = bytes.NewBufferString("") rt.buf = bytes.NewBufferString("")
printConfiguration(rt.cfg, rt.buf) printConfiguration(rt.cfg, rt.buf, &output.TextPrinter{})
actualBytes := rt.buf.Bytes() actualBytes := rt.buf.Bytes()
if !bytes.Equal(actualBytes, rt.expectedBytes) { if !bytes.Equal(actualBytes, rt.expectedBytes) {
t.Errorf( t.Errorf(

View File

@ -118,7 +118,7 @@ func runDiff(flags *diffFlags, args []string) error {
if err != nil { if err != nil {
return errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath) return errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath)
} }
cfg, err = configutil.FetchInitConfigurationFromCluster(client, flags.out, "upgrade/diff", false, false) cfg, err = configutil.FetchInitConfigurationFromCluster(client, nil, "upgrade/diff", false, false)
} }
if err != nil { if err != nil {
return err return err

View File

@ -139,7 +139,7 @@ func newNodeData(cmd *cobra.Command, args []string, options *nodeOptions) (*node
// Fetches the cluster configuration // Fetches the cluster configuration
// NB in case of control-plane node, we are reading all the info for the node; in case of NOT control-plane node // NB in case of control-plane node, we are reading all the info for the node; in case of NOT control-plane node
// (worker node), we are not reading local API address and the CRI socket from the node object // (worker node), we are not reading local API address and the CRI socket from the node object
cfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, "upgrade", !isControlPlaneNode, false) cfg, err := configutil.FetchInitConfigurationFromCluster(client, nil, "upgrade", !isControlPlaneNode, false)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap") return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap")
} }

View File

@ -28,16 +28,21 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/version" "k8s.io/apimachinery/pkg/util/version"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/klog/v2" "k8s.io/klog/v2"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
outputapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/output" outputapischeme "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/scheme"
outputapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs" "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
"k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" "k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
) )
type planFlags struct { type planFlags struct {
@ -50,25 +55,202 @@ func newCmdPlan(apf *applyPlanFlags) *cobra.Command {
applyPlanFlags: apf, applyPlanFlags: apf,
} }
outputFlags := newUpgradePlanPrintFlags(output.TextOutput)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "plan [version] [flags]", Use: "plan [version] [flags]",
Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable. To skip the internet check, pass in the optional [version] parameter", Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable. To skip the internet check, pass in the optional [version] parameter",
RunE: func(_ *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
return runPlan(flags, args) printer, err := outputFlags.ToPrinter()
if err != nil {
return errors.Wrap(err, "could not construct output printer")
}
return runPlan(flags, args, printer)
}, },
} }
outputFlags.AddFlags(cmd)
// Register the common flags for apply and plan // Register the common flags for apply and plan
addApplyPlanFlags(cmd.Flags(), flags.applyPlanFlags) addApplyPlanFlags(cmd.Flags(), flags.applyPlanFlags)
return cmd return cmd
} }
// newComponentUpgradePlan helper creates outputapiv1alpha2.ComponentUpgradePlan object
func newComponentUpgradePlan(name, currentVersion, newVersion string) outputapiv1alpha2.ComponentUpgradePlan {
return outputapiv1alpha2.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
Buffer []outputapiv1alpha2.ComponentUpgradePlan
}
// 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.Buffer = append(p.Buffer, *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.Buffer) == 0 {
return
}
plan := &outputapiv1alpha2.UpgradePlan{Components: p.Buffer}
if err := p.Printer.PrintObj(plan, writer); err != nil {
fmt.Fprintf(os.Stderr, "could not flush output buffer: %v\n", err)
}
}
// Close empties the list of buffered components
func (p *upgradePlanJSONYAMLPrinter) Close(writer io.Writer) {
p.Buffer = p.Buffer[:0]
}
// 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 // runPlan takes care of outputting available versions to upgrade to for the user
func runPlan(flags *planFlags, args []string) error { 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. // Start with the basics, verify that the cluster is healthy, build a client and a versionGetter. Never dry-run when planning.
klog.V(1).Infoln("[upgrade/plan] verifying health of cluster") klog.V(1).Infoln("[upgrade/plan] verifying health of cluster")
klog.V(1).Infoln("[upgrade/plan] retrieving configuration from cluster") klog.V(1).Infoln("[upgrade/plan] retrieving configuration from cluster")
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, false, false) client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, false, false, printer)
if err != nil { if err != nil {
return err return err
} }
@ -79,7 +261,7 @@ func runPlan(flags *planFlags, args []string) error {
// Compute which upgrade possibilities there are // Compute which upgrade possibilities there are
klog.V(1).Infoln("[upgrade/plan] computing upgrade possibilities") klog.V(1).Infoln("[upgrade/plan] computing upgrade possibilities")
availUpgrades, err := upgrade.GetAvailableUpgrades(versionGetter, flags.allowExperimentalUpgrades, flags.allowRCUpgrades, isExternalEtcd, client, constants.GetStaticPodDirectory()) availUpgrades, err := upgrade.GetAvailableUpgrades(versionGetter, flags.allowExperimentalUpgrades, flags.allowRCUpgrades, isExternalEtcd, client, constants.GetStaticPodDirectory(), printer)
if err != nil { if err != nil {
return errors.Wrap(err, "[upgrade/versions] FATAL") return errors.Wrap(err, "[upgrade/versions] FATAL")
} }
@ -109,27 +291,17 @@ func runPlan(flags *planFlags, args []string) error {
// in the human readable output if it did so // in the human readable output if it did so
plan.ConfigVersions = configVersionStates plan.ConfigVersions = configVersionStates
printUpgradePlan(&up, plan, unstableVersionFlag, isExternalEtcd, os.Stdout) printUpgradePlan(&up, plan, unstableVersionFlag, isExternalEtcd, os.Stdout, printer)
} }
// Finally, print the component config state table // Finally, print the component config state table
printComponentConfigVersionStates(configVersionStates, os.Stdout) printComponentConfigVersionStates(configVersionStates, os.Stdout, printer)
return nil return nil
} }
// newComponentUpgradePlan helper creates outputapi.ComponentUpgradePlan object
func newComponentUpgradePlan(name, currentVersion, newVersion string) outputapi.ComponentUpgradePlan {
return outputapi.ComponentUpgradePlan{
Name: name,
CurrentVersion: currentVersion,
NewVersion: newVersion,
}
}
// TODO There is currently no way to cleanly output upgrades that involve adding, removing, or changing components // 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. // https://github.com/kubernetes/kubeadm/issues/810 was created to track addressing this.
func appendDNSComponent(components []outputapi.ComponentUpgradePlan, up *upgrade.Upgrade, name string) []outputapi.ComponentUpgradePlan { func appendDNSComponent(components []outputapiv1alpha2.ComponentUpgradePlan, up *upgrade.Upgrade, name string) []outputapiv1alpha2.ComponentUpgradePlan {
beforeVersion := up.Before.DNSVersion beforeVersion := up.Before.DNSVersion
afterVersion := up.After.DNSVersion afterVersion := up.After.DNSVersion
@ -140,7 +312,7 @@ func appendDNSComponent(components []outputapi.ComponentUpgradePlan, up *upgrade
} }
// genUpgradePlan generates output-friendly upgrade plan out of upgrade.Upgrade structure // genUpgradePlan generates output-friendly upgrade plan out of upgrade.Upgrade structure
func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapi.UpgradePlan, string, error) { func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapiv1alpha2.UpgradePlan, string, error) {
newK8sVersion, err := version.ParseSemantic(up.After.KubeVersion) newK8sVersion, err := version.ParseSemantic(up.After.KubeVersion)
if err != nil { if err != nil {
return nil, "", errors.Wrapf(err, "Unable to parse normalized version %q as a semantic version", up.After.KubeVersion) return nil, "", errors.Wrapf(err, "Unable to parse normalized version %q as a semantic version", up.After.KubeVersion)
@ -155,7 +327,7 @@ func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapi.Upgrad
} }
} }
components := []outputapi.ComponentUpgradePlan{} components := []outputapiv1alpha2.ComponentUpgradePlan{}
if up.CanUpgradeKubelets() { if up.CanUpgradeKubelets() {
// The map is of the form <old-version>:<node-count>. Here all the keys are put into a slice and sorted // The map is of the form <old-version>:<node-count>. Here all the keys are put into a slice and sorted
@ -177,10 +349,10 @@ func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapi.Upgrad
components = append(components, newComponentUpgradePlan(constants.Etcd, up.Before.EtcdVersion, up.After.EtcdVersion)) components = append(components, newComponentUpgradePlan(constants.Etcd, up.Before.EtcdVersion, up.After.EtcdVersion))
} }
return &outputapi.UpgradePlan{Components: components}, unstableVersionFlag, nil return &outputapiv1alpha2.UpgradePlan{Components: components}, unstableVersionFlag, nil
} }
func getComponentConfigVersionStates(cfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, cfgPath string) ([]outputapi.ComponentConfigVersionState, error) { func getComponentConfigVersionStates(cfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, cfgPath string) ([]outputapiv1alpha2.ComponentConfigVersionState, error) {
docmap := kubeadmapi.DocumentMap{} docmap := kubeadmapi.DocumentMap{}
if cfgPath != "" { if cfgPath != "" {
@ -199,16 +371,7 @@ func getComponentConfigVersionStates(cfg *kubeadmapi.ClusterConfiguration, clien
} }
// printUpgradePlan prints a UX-friendly overview of what versions are available to upgrade to // printUpgradePlan prints a UX-friendly overview of what versions are available to upgrade to
func printUpgradePlan(up *upgrade.Upgrade, plan *outputapi.UpgradePlan, unstableVersionFlag string, isExternalEtcd bool, w io.Writer) { func printUpgradePlan(up *upgrade.Upgrade, plan *outputapiv1alpha2.UpgradePlan, unstableVersionFlag string, isExternalEtcd bool, writer io.Writer, printer output.Printer) {
// The tab writer writes to the "real" writer w
tabw := tabwriter.NewWriter(w, 10, 4, 3, ' ', 0)
// endOfTable helper function flashes table writer
endOfTable := func() {
tabw.Flush()
fmt.Fprintln(w, "")
}
printHeader := true printHeader := true
printManualUpgradeHeader := true printManualUpgradeHeader := true
for _, component := range plan.Components { for _, component := range plan.Components {
@ -217,41 +380,40 @@ func printUpgradePlan(up *upgrade.Upgrade, plan *outputapi.UpgradePlan, unstable
continue continue
} else if component.Name == constants.Kubelet { } else if component.Name == constants.Kubelet {
if printManualUpgradeHeader { if printManualUpgradeHeader {
fmt.Fprintln(w, "Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':") printer.Fprintln(writer, "Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':")
fmt.Fprintln(tabw, "COMPONENT\tCURRENT\tTARGET") plan := newComponentUpgradePlan(component.Name, component.CurrentVersion, component.NewVersion)
fmt.Fprintf(tabw, "%s\t%s\t%s\n", component.Name, component.CurrentVersion, component.NewVersion) printer.PrintObj(&plan, writer)
printManualUpgradeHeader = false printManualUpgradeHeader = false
} else { } else {
fmt.Fprintf(tabw, "%s\t%s\t%s\n", "", component.CurrentVersion, component.NewVersion) plan := newComponentUpgradePlan("", component.CurrentVersion, component.NewVersion)
printer.PrintObj(&plan, writer)
} }
} else { } else {
if printHeader { if printHeader {
// End of manual upgrades table // End of manual upgrades table
endOfTable() printer.Flush(writer, false)
printer.Fprintf(writer, "Upgrade to the latest %s:\n", up.Description)
fmt.Fprintf(w, "Upgrade to the latest %s:\n", up.Description) printer.Fprintln(writer, "")
fmt.Fprintln(w, "")
fmt.Fprintln(tabw, "COMPONENT\tCURRENT\tTARGET")
printHeader = false printHeader = false
} }
fmt.Fprintf(tabw, "%s\t%s\t%s\n", component.Name, component.CurrentVersion, component.NewVersion) plan := newComponentUpgradePlan(component.Name, component.CurrentVersion, component.NewVersion)
printer.PrintObj(&plan, writer)
} }
} }
// End of control plane table printer.Flush(writer, true)
endOfTable()
//fmt.Fprintln(w, "") printer.Fprintln(writer, "You can now apply the upgrade by executing the following command:")
fmt.Fprintln(w, "You can now apply the upgrade by executing the following command:") printer.Fprintln(writer, "")
fmt.Fprintln(w, "") printer.Fprintf(writer, "\tkubeadm upgrade apply %s%s\n", up.After.KubeVersion, unstableVersionFlag)
fmt.Fprintf(w, "\tkubeadm upgrade apply %s%s\n", up.After.KubeVersion, unstableVersionFlag) printer.Fprintln(writer, "")
fmt.Fprintln(w, "")
if up.Before.KubeadmVersion != up.After.KubeadmVersion { if up.Before.KubeadmVersion != up.After.KubeadmVersion {
fmt.Fprintf(w, "Note: Before you can perform this upgrade, you have to update kubeadm to %s.\n", up.After.KubeadmVersion) printer.Fprintf(writer, "Note: Before you can perform this upgrade, you have to update kubeadm to %s.\n", up.After.KubeadmVersion)
fmt.Fprintln(w, "") printer.Fprintln(writer, "")
} }
printLineSeparator(w) printLineSeparator(writer, printer)
printer.Close(writer)
} }
// sortedSliceFromStringIntMap returns a slice of the keys in the map sorted alphabetically // sortedSliceFromStringIntMap returns a slice of the keys in the map sorted alphabetically
@ -278,18 +440,17 @@ func yesOrNo(b bool) string {
return "no" return "no"
} }
func printLineSeparator(w io.Writer) { func printLineSeparator(w io.Writer, printer output.Printer) {
fmt.Fprintln(w, "_____________________________________________________________________") printer.Fprintf(w, "_____________________________________________________________________\n\n")
fmt.Fprintln(w, "")
} }
func printComponentConfigVersionStates(versionStates []outputapi.ComponentConfigVersionState, w io.Writer) { func printComponentConfigVersionStates(versionStates []outputapiv1alpha2.ComponentConfigVersionState, w io.Writer, printer output.Printer) {
if len(versionStates) == 0 { if len(versionStates) == 0 {
fmt.Fprintln(w, "No information available on component configs.") printer.Fprintln(w, "No information available on component configs.")
return return
} }
fmt.Fprintln(w, dedent.Dedent(` printer.Fprintln(w, dedent.Dedent(`
The table below shows the current state of component configs as understood by this version of kubeadm. 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 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 resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually
@ -297,10 +458,10 @@ func printComponentConfigVersionStates(versionStates []outputapi.ComponentConfig
`)) `))
tabw := tabwriter.NewWriter(w, 10, 4, 3, ' ', 0) tabw := tabwriter.NewWriter(w, 10, 4, 3, ' ', 0)
fmt.Fprintln(tabw, "API GROUP\tCURRENT VERSION\tPREFERRED VERSION\tMANUAL UPGRADE REQUIRED") printer.Fprintln(tabw, "API GROUP\tCURRENT VERSION\tPREFERRED VERSION\tMANUAL UPGRADE REQUIRED")
for _, state := range versionStates { for _, state := range versionStates {
fmt.Fprintf(tabw, printer.Fprintf(tabw,
"%s\t%s\t%s\t%s\n", "%s\t%s\t%s\t%s\n",
state.Group, state.Group,
strOrDash(state.CurrentVersion), strOrDash(state.CurrentVersion),
@ -310,5 +471,5 @@ func printComponentConfigVersionStates(versionStates []outputapi.ComponentConfig
} }
tabw.Flush() tabw.Flush()
printLineSeparator(w) printLineSeparator(w, printer)
} }

View File

@ -22,6 +22,7 @@ import (
"testing" "testing"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" "k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
) )
func TestSortedSliceFromStringIntMap(t *testing.T) { func TestSortedSliceFromStringIntMap(t *testing.T) {
@ -441,13 +442,19 @@ _____________________________________________________________________
for _, rt := range tests { for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) { t.Run(rt.name, func(t *testing.T) {
rt.buf = bytes.NewBufferString("") rt.buf = bytes.NewBufferString("")
outputFlags := newUpgradePlanPrintFlags(output.TextOutput)
printer, err := outputFlags.ToPrinter()
if err != nil {
t.Errorf("failed ToPrinter, err: %+v", err)
}
// Generate and print upgrade plans // Generate and print upgrade plans
for _, up := range rt.upgrades { for _, up := range rt.upgrades {
plan, unstableVersionFlag, err := genUpgradePlan(&up, rt.externalEtcd) plan, unstableVersionFlag, err := genUpgradePlan(&up, rt.externalEtcd)
if err != nil { if err != nil {
t.Errorf("failed genUpgradePlan, err: %+v", err) t.Errorf("failed genUpgradePlan, err: %+v", err)
} }
printUpgradePlan(&up, plan, unstableVersionFlag, rt.externalEtcd, rt.buf) printUpgradePlan(&up, plan, unstableVersionFlag, rt.externalEtcd, rt.buf, printer)
} }
actualBytes := rt.buf.Bytes() actualBytes := rt.buf.Bytes()
if !bytes.Equal(actualBytes, rt.expectedBytes) { if !bytes.Equal(actualBytes, rt.expectedBytes) {
@ -460,3 +467,164 @@ _____________________________________________________________________
}) })
} }
} }
func TestPrintAvailableUpgradesStructured(t *testing.T) {
upgrades := []upgrade.Upgrade{
{
Description: "version in the v1.8 series",
Before: upgrade.ClusterState{
KubeVersion: "v1.8.1",
KubeletVersions: map[string]uint16{
"v1.8.1": 1,
},
KubeadmVersion: "v1.8.2",
DNSVersion: "1.14.5",
EtcdVersion: "3.0.17",
},
After: upgrade.ClusterState{
KubeVersion: "v1.8.3",
KubeadmVersion: "v1.8.3",
DNSVersion: "1.14.5",
EtcdVersion: "3.0.17",
},
},
}
var tests = []struct {
name string
outputFormat string
buf *bytes.Buffer
expected string
externalEtcd bool
}{
{
name: "JSON output",
outputFormat: "json",
expected: `{
"kind": "UpgradePlan",
"apiVersion": "output.kubeadm.k8s.io/v1alpha2",
"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": "etcd",
"currentVersion": "3.0.17",
"newVersion": "3.0.17"
}
],
"configVersions": null
}
`,
},
{
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
kind: UpgradePlan
`,
},
{
name: "Text output",
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
Upgrade to the latest version in the v1.8 series:
COMPONENT CURRENT TARGET
kube-apiserver v1.8.1 v1.8.3
kube-controller-manager v1.8.1 v1.8.3
kube-scheduler v1.8.1 v1.8.3
kube-proxy v1.8.1 v1.8.3
CoreDNS 1.14.5 1.14.5
etcd 3.0.17 3.0.17
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.8.3
Note: Before you can perform this upgrade, you have to update kubeadm to v1.8.3.
_____________________________________________________________________
`,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
rt.buf = bytes.NewBufferString("")
outputFlags := newUpgradePlanPrintFlags(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)
}
actual := rt.buf.String()
if actual != rt.expected {
t.Errorf("failed PrintAvailableUpgrades:\n\nexpected:\n%s\n\nactual:\n%s", rt.expected, actual)
}
})
}
}

View File

@ -28,7 +28,7 @@ import (
"k8s.io/klog/v2" "k8s.io/klog/v2"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/output" outputapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha2"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
"k8s.io/kubernetes/cmd/kubeadm/app/util/config/strict" "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 // GetVersionStates returns a slice of ComponentConfigVersionState structs
// describing all supported component config groups that were identified on the cluster // describing all supported component config groups that were identified on the cluster
func GetVersionStates(clusterCfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, docmap kubeadmapi.DocumentMap) ([]output.ComponentConfigVersionState, error) { func GetVersionStates(clusterCfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, docmap kubeadmapi.DocumentMap) ([]outputapiv1alpha2.ComponentConfigVersionState, error) {
// We don't want to modify clusterCfg so we make a working deep copy of it. // 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. // Also, we don't want the defaulted component configs so we get rid of them.
scratchClusterCfg := clusterCfg.DeepCopy() scratchClusterCfg := clusterCfg.DeepCopy()
@ -308,13 +308,13 @@ func GetVersionStates(clusterCfg *kubeadmapi.ClusterConfiguration, client client
} }
} }
results := []output.ComponentConfigVersionState{} results := []outputapiv1alpha2.ComponentConfigVersionState{}
for _, handler := range known { for _, handler := range known {
group := handler.GroupVersion.Group group := handler.GroupVersion.Group
if vererr, ok := multipleVerErrs[group]; ok { if vererr, ok := multipleVerErrs[group]; ok {
// If there is an UnsupportedConfigVersionError then we are dealing with a case where the config was user // If there is an UnsupportedConfigVersionError then we are dealing with a case where the config was user
// supplied and requires manual upgrade // supplied and requires manual upgrade
results = append(results, output.ComponentConfigVersionState{ results = append(results, outputapiv1alpha2.ComponentConfigVersionState{
Group: group, Group: group,
CurrentVersion: vererr.OldVersion.Version, CurrentVersion: vererr.OldVersion.Version,
PreferredVersion: vererr.CurrentVersion.Version, PreferredVersion: vererr.CurrentVersion.Version,
@ -322,7 +322,7 @@ func GetVersionStates(clusterCfg *kubeadmapi.ClusterConfiguration, client client
}) })
} else if _, ok := scratchClusterCfg.ComponentConfigs[group]; ok { } else if _, ok := scratchClusterCfg.ComponentConfigs[group]; ok {
// Normally loaded component config. No manual upgrade required on behalf of users. // Normally loaded component config. No manual upgrade required on behalf of users.
results = append(results, output.ComponentConfigVersionState{ results = append(results, outputapiv1alpha2.ComponentConfigVersionState{
Group: group, Group: group,
CurrentVersion: handler.GroupVersion.Version, // Currently kubeadm supports only one version per API 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 PreferredVersion: handler.GroupVersion.Version, // group so we can get away with these being the same
@ -331,7 +331,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 // 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 // generated one and is therefore skipped so we can automatically re-generate it (no action required on
// behalf of the user). // behalf of the user).
results = append(results, output.ComponentConfigVersionState{ results = append(results, outputapiv1alpha2.ComponentConfigVersionState{
Group: group, Group: group,
PreferredVersion: handler.GroupVersion.Version, PreferredVersion: handler.GroupVersion.Version,
}) })

View File

@ -35,7 +35,7 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3" kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
outputapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/output" outputapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
) )
@ -615,12 +615,12 @@ func TestFetchFromClusterWithLocalOverwrites(t *testing.T) {
func TestGetVersionStates(t *testing.T) { func TestGetVersionStates(t *testing.T) {
fakeKnownContext(func() { fakeKnownContext(func() {
versionStateCurrent := outputapi.ComponentConfigVersionState{ versionStateCurrent := outputapiv1alpha2.ComponentConfigVersionState{
Group: kubeadmapiv1.GroupName, Group: kubeadmapiv1.GroupName,
CurrentVersion: currentClusterConfigVersion, CurrentVersion: currentClusterConfigVersion,
PreferredVersion: currentClusterConfigVersion, PreferredVersion: currentClusterConfigVersion,
} }
versionStateOld := outputapi.ComponentConfigVersionState{ versionStateOld := outputapiv1alpha2.ComponentConfigVersionState{
Group: kubeadmapiv1.GroupName, Group: kubeadmapiv1.GroupName,
CurrentVersion: oldClusterConfigVersion, CurrentVersion: oldClusterConfigVersion,
PreferredVersion: currentClusterConfigVersion, PreferredVersion: currentClusterConfigVersion,
@ -631,7 +631,7 @@ func TestGetVersionStates(t *testing.T) {
desc string desc string
obj runtime.Object obj runtime.Object
config string config string
expected outputapi.ComponentConfigVersionState expected outputapiv1alpha2.ComponentConfigVersionState
}{ }{
{ {
desc: "appropriate cluster object without overwrite", desc: "appropriate cluster object without overwrite",
@ -687,7 +687,7 @@ func TestGetVersionStates(t *testing.T) {
{ {
desc: "old signed config without an overwrite", desc: "old signed config without an overwrite",
obj: testClusterConfigMap(oldFooClusterConfig, true), obj: testClusterConfigMap(oldFooClusterConfig, true),
expected: outputapi.ComponentConfigVersionState{ expected: outputapiv1alpha2.ComponentConfigVersionState{
Group: kubeadmapiv1.GroupName, Group: kubeadmapiv1.GroupName,
CurrentVersion: "", // The config is treated as if it's missing CurrentVersion: "", // The config is treated as if it's missing
PreferredVersion: currentClusterConfigVersion, PreferredVersion: currentClusterConfigVersion,

View File

@ -26,6 +26,7 @@ import (
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns" "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns"
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
) )
// Upgrade defines an upgrade possibility to upgrade from a current version to a new one // Upgrade defines an upgrade possibility to upgrade from a current version to a new one
@ -72,8 +73,8 @@ type ClusterState struct {
// GetAvailableUpgrades fetches all versions from the specified VersionGetter and computes which // GetAvailableUpgrades fetches all versions from the specified VersionGetter and computes which
// kinds of upgrades can be performed // kinds of upgrades can be performed
func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesAllowed, rcUpgradesAllowed, externalEtcd bool, client clientset.Interface, manifestsDir string) ([]Upgrade, error) { func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesAllowed, rcUpgradesAllowed, externalEtcd bool, client clientset.Interface, manifestsDir string, printer output.Printer) ([]Upgrade, error) {
fmt.Println("[upgrade] Fetching available versions to upgrade to") printer.Printf("[upgrade] Fetching available versions to upgrade to\n")
// Collect the upgrades kubeadm can do in this list // Collect the upgrades kubeadm can do in this list
upgrades := []Upgrade{} upgrades := []Upgrade{}
@ -83,14 +84,14 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA
if err != nil { if err != nil {
return upgrades, err return upgrades, err
} }
fmt.Printf("[upgrade/versions] Cluster version: %s\n", clusterVersionStr) printer.Printf("[upgrade/versions] Cluster version: %s\n", clusterVersionStr)
// Get current kubeadm CLI version // Get current kubeadm CLI version
kubeadmVersionStr, kubeadmVersion, err := versionGetterImpl.KubeadmVersion() kubeadmVersionStr, kubeadmVersion, err := versionGetterImpl.KubeadmVersion()
if err != nil { if err != nil {
return upgrades, err return upgrades, err
} }
fmt.Printf("[upgrade/versions] kubeadm version: %s\n", kubeadmVersionStr) printer.Printf("[upgrade/versions] kubeadm version: %s\n", kubeadmVersionStr)
// Get and output the current latest stable version // Get and output the current latest stable version
stableVersionStr, stableVersion, err := versionGetterImpl.VersionFromCILabel("stable", "stable version") stableVersionStr, stableVersion, err := versionGetterImpl.VersionFromCILabel("stable", "stable version")
@ -99,7 +100,7 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA
klog.Warningf("[upgrade/versions] WARNING: Falling back to current kubeadm version as latest stable version") klog.Warningf("[upgrade/versions] WARNING: Falling back to current kubeadm version as latest stable version")
stableVersionStr, stableVersion = kubeadmVersionStr, kubeadmVersion stableVersionStr, stableVersion = kubeadmVersionStr, kubeadmVersion
} else { } else {
fmt.Printf("[upgrade/versions] Target version: %s\n", stableVersionStr) printer.Printf("[upgrade/versions] Target version: %s\n", stableVersionStr)
} }
// Get the kubelet versions in the cluster // Get the kubelet versions in the cluster
@ -148,7 +149,7 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA
if err != nil { if err != nil {
klog.Warningf("[upgrade/versions] WARNING: %v\n", err) klog.Warningf("[upgrade/versions] WARNING: %v\n", err)
} else { } else {
fmt.Printf("[upgrade/versions] Latest %s: %s\n", description, patchVersionStr) printer.Printf("[upgrade/versions] Latest %s: %s\n", description, patchVersionStr)
// Check if a minor version upgrade is possible when a patch release exists // Check if a minor version upgrade is possible when a patch release exists
// It's only possible if the latest patch version is higher than the current patch version // It's only possible if the latest patch version is higher than the current patch version
@ -267,7 +268,7 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA
} }
// Add a newline in the end of this output to leave some space to the next output section // Add a newline in the end of this output to leave some space to the next output section
fmt.Println("") printer.Println()
return upgrades, nil return upgrades, nil
} }

View File

@ -31,6 +31,7 @@ import (
clientsetfake "k8s.io/client-go/kubernetes/fake" clientsetfake "k8s.io/client-go/kubernetes/fake"
"k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
) )
type fakeVersionGetter struct { type fakeVersionGetter struct {
@ -652,7 +653,7 @@ func TestGetAvailableUpgrades(t *testing.T) {
t.Fatalf("Unable to create test static pod manifest: %v", err) t.Fatalf("Unable to create test static pod manifest: %v", err)
} }
actualUpgrades, actualErr := GetAvailableUpgrades(rt.vg, rt.allowExperimental, rt.allowRCs, rt.externalEtcd, client, manifestsDir) actualUpgrades, actualErr := GetAvailableUpgrades(rt.vg, rt.allowExperimental, rt.allowRCs, rt.externalEtcd, client, manifestsDir, &output.TextPrinter{})
if !reflect.DeepEqual(actualUpgrades, rt.expectedUpgrades) { if !reflect.DeepEqual(actualUpgrades, rt.expectedUpgrades) {
t.Errorf("failed TestGetAvailableUpgrades\n\texpected upgrades: %v\n\tgot: %v", rt.expectedUpgrades, actualUpgrades) t.Errorf("failed TestGetAvailableUpgrades\n\texpected upgrades: %v\n\tgot: %v", rt.expectedUpgrades, actualUpgrades)
} }

View File

@ -20,7 +20,6 @@ import (
"context" "context"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"io"
"path/filepath" "path/filepath"
"strings" "strings"
@ -42,12 +41,16 @@ import (
"k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
"k8s.io/kubernetes/cmd/kubeadm/app/util/config/strict" "k8s.io/kubernetes/cmd/kubeadm/app/util/config/strict"
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
) )
// FetchInitConfigurationFromCluster fetches configuration from a ConfigMap in the cluster // FetchInitConfigurationFromCluster fetches configuration from a ConfigMap in the cluster
func FetchInitConfigurationFromCluster(client clientset.Interface, w io.Writer, logPrefix string, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) { func FetchInitConfigurationFromCluster(client clientset.Interface, printer output.Printer, logPrefix string, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) {
fmt.Fprintf(w, "[%s] Reading configuration from the cluster...\n", logPrefix) if printer == nil {
fmt.Fprintf(w, "[%s] FYI: You can look at this config file with 'kubectl -n %s get cm %s -o yaml'\n", logPrefix, metav1.NamespaceSystem, constants.KubeadmConfigConfigMap) printer = &output.TextPrinter{}
}
printer.Printf("[%s] Reading configuration from the cluster...\n", logPrefix)
printer.Printf("[%s] FYI: You can look at this config file with 'kubectl -n %s get cm %s -o yaml'\n", logPrefix, metav1.NamespaceSystem, constants.KubeadmConfigConfigMap)
// Fetch the actual config from cluster // Fetch the actual config from cluster
cfg, err := getInitConfigurationFromCluster(constants.KubernetesDir, client, newControlPlane, skipComponentConfigs) cfg, err := getInitConfigurationFromCluster(constants.KubernetesDir, client, newControlPlane, skipComponentConfigs)

View File

@ -28,8 +28,16 @@ import (
"k8s.io/cli-runtime/pkg/printers" "k8s.io/cli-runtime/pkg/printers"
) )
// TextOutput describes the plain text output const (
const TextOutput = "text" // TextOutput describes the plain text output
TextOutput = "text"
// JSONOutput describes the JSON output
JSONOutput = "json"
// YAMLOutput describes the YAML output
YAMLOutput = "yaml"
)
// TextPrintFlags is an interface to handle custom text output // TextPrintFlags is an interface to handle custom text output
type TextPrintFlags interface { type TextPrintFlags interface {
@ -44,7 +52,7 @@ type PrintFlags struct {
JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags
// KubeTemplatePrintFlags composes print flags that provide both a JSONPath and a go-template printer. // KubeTemplatePrintFlags composes print flags that provide both a JSONPath and a go-template printer.
KubeTemplatePrintFlags *genericclioptions.KubeTemplatePrintFlags KubeTemplatePrintFlags *genericclioptions.KubeTemplatePrintFlags
// JSONYamlPrintFlags provides default flags necessary for kubeadm text printing. // TextPrintFlags provides default flags necessary for kubeadm text printing.
TextPrintFlags TextPrintFlags TextPrintFlags TextPrintFlags
// TypeSetterPrinter is an implementation of ResourcePrinter that wraps another printer with types set on the objects // TypeSetterPrinter is an implementation of ResourcePrinter that wraps another printer with types set on the objects
TypeSetterPrinter *printers.TypeSetterPrinter TypeSetterPrinter *printers.TypeSetterPrinter
@ -52,7 +60,7 @@ type PrintFlags struct {
OutputFormat *string OutputFormat *string
} }
// AllowedFormats returns list of allowed output formats // AllowedFormats returns a list of allowed output formats
func (pf *PrintFlags) AllowedFormats() []string { func (pf *PrintFlags) AllowedFormats() []string {
ret := []string{TextOutput} ret := []string{TextOutput}
ret = append(ret, pf.JSONYamlPrintFlags.AllowedFormats()...) ret = append(ret, pf.JSONYamlPrintFlags.AllowedFormats()...)
@ -133,7 +141,12 @@ func NewOutputFlags(textPrintFlags TextPrintFlags) *PrintFlags {
type Printer interface { type Printer interface {
PrintObj(obj runtime.Object, writer io.Writer) error PrintObj(obj runtime.Object, writer io.Writer) error
Fprintf(writer io.Writer, format string, args ...interface{}) (n int, err error) Fprintf(writer io.Writer, format string, args ...interface{}) (n int, err error)
Fprintln(writer io.Writer, args ...interface{}) (n int, err error)
Printf(format string, 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 // TextPrinter implements Printer interface for generic text output
@ -151,11 +164,29 @@ func (tp *TextPrinter) Fprintf(writer io.Writer, format string, args ...interfac
return fmt.Fprintf(writer, format, args...) return fmt.Fprintf(writer, format, args...)
} }
// Fprintln is a wrapper around fmt.Fprintln
func (tp *TextPrinter) Fprintln(writer io.Writer, args ...interface{}) (n int, err error) {
return fmt.Fprintln(writer, args...)
}
// Printf is a wrapper around fmt.Printf // Printf is a wrapper around fmt.Printf
func (tp *TextPrinter) Printf(format string, args ...interface{}) (n int, err error) { func (tp *TextPrinter) Printf(format string, args ...interface{}) (n int, err error) {
return fmt.Printf(format, args...) return fmt.Printf(format, args...)
} }
// Println is a wrapper around fmt.Printf
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 // ResourcePrinterWrapper wraps ResourcePrinter and implements Printer interface
type ResourcePrinterWrapper struct { type ResourcePrinterWrapper struct {
Printer printers.ResourcePrinter Printer printers.ResourcePrinter
@ -169,6 +200,14 @@ func NewResourcePrinterWrapper(resourcePrinter printers.ResourcePrinter, err err
return &ResourcePrinterWrapper{Printer: resourcePrinter}, nil 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 // PrintObj is an implementation of ResourcePrinter.PrintObj that calls underlying printer API
func (rpw *ResourcePrinterWrapper) PrintObj(obj runtime.Object, writer io.Writer) error { func (rpw *ResourcePrinterWrapper) PrintObj(obj runtime.Object, writer io.Writer) error {
return rpw.Printer.PrintObj(obj, writer) return rpw.Printer.PrintObj(obj, writer)
@ -181,9 +220,23 @@ func (rpw *ResourcePrinterWrapper) Fprintf(writer io.Writer, format string, args
return 0, nil return 0, nil
} }
// Fprintln is an empty method to satisfy the Printer interface
// and silent info printing for structured output
// This method is usually redefined for the text output
func (rpw *ResourcePrinterWrapper) Fprintln(writer io.Writer, args ...interface{}) (n int, err error) {
return 0, nil
}
// Printf is an empty method to satisfy Printer interface // Printf is an empty method to satisfy Printer interface
// and silent info printing for structured output // and silent info printing for structured output
// This method is usually redefined for the text output // This method is usually redefined for the text output
func (rpw *ResourcePrinterWrapper) Printf(format string, args ...interface{}) (n int, err error) { func (rpw *ResourcePrinterWrapper) Printf(format string, args ...interface{}) (n int, err error) {
return 0, nil return 0, nil
} }
// Println is an empty method to satisfy Printer interface
// and silent info printing for structured output
// This method is usually redefined for the text output
func (rpw *ResourcePrinterWrapper) Println(args ...interface{}) (n int, err error) {
return 0, nil
}