feature(kubeadm): add support of json/yaml format for upgrade plan

- cherry-pick 83941 and rebase
This commit is contained in:
Paco Xu 2022-03-01 15:11:23 +08:00
parent 2de37aa9fa
commit 93daef6e77
16 changed files with 300 additions and 67 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

@ -39,6 +39,7 @@ import (
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
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"
) )
var ( var (
@ -332,7 +333,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, &output.TextPrinter{}, 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

@ -47,6 +47,7 @@ import (
"k8s.io/kubernetes/cmd/kubeadm/app/discovery" "k8s.io/kubernetes/cmd/kubeadm/app/discovery"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
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"
) )
var ( var (
@ -618,7 +619,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, &output.TextPrinter{}, "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

@ -37,6 +37,7 @@ import (
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
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"
utilruntime "k8s.io/kubernetes/cmd/kubeadm/app/util/runtime" utilruntime "k8s.io/kubernetes/cmd/kubeadm/app/util/runtime"
) )
@ -98,7 +99,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, &output.TextPrinter{}, "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

@ -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,17 +122,17 @@ 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) fmt.Printf("[upgrade/config] In order to upgrade, a ConfigMap called %q in the %s namespace must exist.\n", constants.KubeadmConfigConfigMap, metav1.NamespaceSystem)
@ -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
} }
@ -235,8 +236,8 @@ func printConfiguration(clustercfg *kubeadmapi.ClusterConfiguration, w io.Writer
} }
// 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")

View File

@ -37,6 +37,7 @@ import (
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
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"
) )
type diffFlags struct { type diffFlags struct {
@ -118,7 +119,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, &output.TextPrinter{}, "upgrade/diff", false, false)
} }
if err != nil { if err != nil {
return err return err

View File

@ -33,6 +33,7 @@ import (
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
"k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/constants"
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"
) )
// nodeOptions defines all the options exposed via flags by kubeadm upgrade node. // nodeOptions defines all the options exposed via flags by kubeadm upgrade node.
@ -139,7 +140,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, &output.TextPrinter{}, "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" 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,188 @@ 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 err
}
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 outputapiv1alpha1.ComponentUpgradePlan object
func newComponentUpgradePlan(name, currentVersion, newVersion string) outputapi.ComponentUpgradePlan {
return outputapi.ComponentUpgradePlan{
Name: name,
CurrentVersion: currentVersion,
NewVersion: newVersion,
}
}
// upgradePlanPrintFlags defines 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 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)
cmd.Flags().StringVarP(&pf.OutputFormat, "experimental-output", "o", pf.OutputFormat, fmt.Sprintf("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 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 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 fmt.Errorf("expected ComponentUpgradePlan, but got %+v", obj)
}
p.Buffer = append(p.Buffer, *item)
return nil
}
// Close writes any buffered data and empties list of buffered components
func (p *upgradePlanJSONYAMLPrinter) Close(writer io.Writer) {
plan := &outputapiv1alpha2.UpgradePlan{Components: p.Buffer}
p.Printer.PrintObj(plan, writer)
p.Buffer = []outputapiv1alpha2.ComponentUpgradePlan{}
}
// 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) {
if p.tabwriter != nil {
p.tabwriter.Flush()
p.tabwriter = nil
}
}
// 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 fmt.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 list of allowed output formats
func (pf *upgradePlanTextPrintFlags) AllowedFormats() []string {
return []string{output.TextOutput}
}
// ToPrinter returns 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", "AVAILABLE"}}, nil
}
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: []string{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,6 +247,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")
klog.V(1).Infoln("[upgrade/plan] Fetching available versions to upgrade to")
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())
if err != nil { if err != nil {
return errors.Wrap(err, "[upgrade/versions] FATAL") return errors.Wrap(err, "[upgrade/versions] FATAL")
@ -109,24 +278,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)
// Add a newline in the end of this output to leave some space to the next output section
klog.V(1).Infoln()
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 []outputapi.ComponentUpgradePlan, up *upgrade.Upgrade, name string) []outputapi.ComponentUpgradePlan {
@ -199,14 +361,14 @@ 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 *outputapi.UpgradePlan, unstableVersionFlag string, isExternalEtcd bool, w io.Writer, printer output.Printer) {
// The tab writer writes to the "real" writer w // The tab writer writes to the "real" writer w
tabw := tabwriter.NewWriter(w, 10, 4, 3, ' ', 0) tabw := tabwriter.NewWriter(w, 10, 4, 3, ' ', 0)
// endOfTable helper function flashes table writer // endOfTable helper function flashes table writer
endOfTable := func() { endOfTable := func() {
tabw.Flush() tabw.Flush()
fmt.Fprintln(w, "") printer.Fprintln(w, "")
} }
printHeader := true printHeader := true
@ -217,41 +379,41 @@ 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(w, "Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':")
fmt.Fprintln(tabw, "COMPONENT\tCURRENT\tTARGET") printer.Fprintln(tabw, "COMPONENT\tCURRENT\tTARGET")
fmt.Fprintf(tabw, "%s\t%s\t%s\n", component.Name, component.CurrentVersion, component.NewVersion) printer.Fprintf(tabw, "%s\t%s\t%s\n", component.Name, component.CurrentVersion, component.NewVersion)
printManualUpgradeHeader = false printManualUpgradeHeader = false
} else { } else {
fmt.Fprintf(tabw, "%s\t%s\t%s\n", "", component.CurrentVersion, component.NewVersion) printer.Fprintf(tabw, "%s\t%s\t%s\n", "", component.CurrentVersion, component.NewVersion)
} }
} else { } else {
if printHeader { if printHeader {
// End of manual upgrades table // End of manual upgrades table
endOfTable() endOfTable()
fmt.Fprintf(w, "Upgrade to the latest %s:\n", up.Description) printer.Fprintf(w, "Upgrade to the latest %s:\n", up.Description)
fmt.Fprintln(w, "") printer.Fprintln(w, "")
fmt.Fprintln(tabw, "COMPONENT\tCURRENT\tTARGET") printer.Fprintln(tabw, "COMPONENT\tCURRENT\tTARGET")
printHeader = false printHeader = false
} }
fmt.Fprintf(tabw, "%s\t%s\t%s\n", component.Name, component.CurrentVersion, component.NewVersion) printer.Fprintf(tabw, "%s\t%s\t%s\n", component.Name, component.CurrentVersion, component.NewVersion)
} }
} }
// End of control plane table // End of control plane table
endOfTable() endOfTable()
//fmt.Fprintln(w, "") //fmt.Fprintln(w, "")
fmt.Fprintln(w, "You can now apply the upgrade by executing the following command:") printer.Fprintln(w, "You can now apply the upgrade by executing the following command:")
fmt.Fprintln(w, "") printer.Fprintln(w, "")
fmt.Fprintf(w, "\tkubeadm upgrade apply %s%s\n", up.After.KubeVersion, unstableVersionFlag) printer.Fprintf(w, "\tkubeadm upgrade apply %s%s\n", up.After.KubeVersion, unstableVersionFlag)
fmt.Fprintln(w, "") printer.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(w, "Note: Before you can perform this upgrade, you have to update kubeadm to %s.\n", up.After.KubeadmVersion)
fmt.Fprintln(w, "") printer.Fprintln(w, "")
} }
printLineSeparator(w) printLineSeparator(w, printer)
} }
// 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,18 @@ 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.Fprintln(w, "_____________________________________________________________________")
fmt.Fprintln(w, "") printer.Fprintln(w, "")
} }
func printComponentConfigVersionStates(versionStates []outputapi.ComponentConfigVersionState, w io.Writer) { func printComponentConfigVersionStates(versionStates []outputapi.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 +459,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 +472,5 @@ func printComponentConfigVersionStates(versionStates []outputapi.ComponentConfig
} }
tabw.Flush() tabw.Flush()
printLineSeparator(w) printLineSeparator(w, printer)
} }

View File

@ -73,8 +73,6 @@ 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) ([]Upgrade, error) {
fmt.Println("[upgrade] Fetching available versions to upgrade to")
// Collect the upgrades kubeadm can do in this list // Collect the upgrades kubeadm can do in this list
upgrades := []Upgrade{} upgrades := []Upgrade{}
@ -266,9 +264,6 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA
} }
} }
// Add a newline in the end of this output to leave some space to the next output section
fmt.Println("")
return upgrades, nil return upgrades, nil
} }

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,13 @@ 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) printer.Printf("[%s] Reading configuration from the cluster...\n", logPrefix)
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.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

@ -31,6 +31,12 @@ import (
// TextOutput describes the plain text output // TextOutput describes the plain text output
const TextOutput = "text" const TextOutput = "text"
// JSONOutput describes the JSON output
const JSONOutput = "json"
// YAMLOutput describes the YAML output
const 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 {
ToPrinter(outputFormat string) (Printer, error) ToPrinter(outputFormat string) (Printer, error)
@ -44,7 +50,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
@ -133,13 +139,27 @@ 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)
Flush(writer io.Writer)
Close(writer io.Writer)
} }
// TextPrinter implements Printer interface for generic text output // TextPrinter implements Printer interface for generic text output
type TextPrinter struct { type TextPrinter struct {
} }
// Flush writes any buffered data
func (tp *TextPrinter) Flush(writer io.Writer) {
return
}
// Close flushes any buffered data and closes the printer
func (tp *TextPrinter) Close(writer io.Writer) {
return
}
// PrintObj is an implementation of ResourcePrinter.PrintObj that prints object // PrintObj is an implementation of ResourcePrinter.PrintObj that prints object
func (tp *TextPrinter) PrintObj(obj runtime.Object, writer io.Writer) error { func (tp *TextPrinter) PrintObj(obj runtime.Object, writer io.Writer) error {
_, err := fmt.Fprintf(writer, "%+v\n", obj) _, err := fmt.Fprintf(writer, "%+v\n", obj)
@ -151,6 +171,11 @@ 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...)
@ -169,6 +194,16 @@ 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) {
return
}
// Close flushes any buffered data and closes the printer
func (rpw *ResourcePrinterWrapper) Close(writer io.Writer) {
return
}
// 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,6 +216,13 @@ func (rpw *ResourcePrinterWrapper) Fprintf(writer io.Writer, format string, args
return 0, nil return 0, nil
} }
// Fprintln 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) 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