mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 03:41:45 +00:00
feature(kubeadm): add support of json/yaml format for upgrade plan
- cherry-pick 83941 and rebase
This commit is contained in:
parent
2de37aa9fa
commit
93daef6e77
@ -40,8 +40,12 @@ type Images struct {
|
||||
Images []string
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// ComponentUpgradePlan represents information about upgrade plan for one component
|
||||
type ComponentUpgradePlan struct {
|
||||
metav1.TypeMeta
|
||||
|
||||
Name string
|
||||
CurrentVersion string
|
||||
NewVersion string
|
||||
|
@ -40,8 +40,12 @@ type Images struct {
|
||||
Images []string `json:"images"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// ComponentUpgradePlan represents information about upgrade plan for one component
|
||||
type ComponentUpgradePlan struct {
|
||||
metav1.TypeMeta
|
||||
|
||||
Name string `json:"name"`
|
||||
CurrentVersion string `json:"currentVersion"`
|
||||
NewVersion string `json:"newVersion"`
|
||||
|
@ -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.
|
||||
func (in *ComponentUpgradePlan) DeepCopyInto(out *ComponentUpgradePlan) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
return
|
||||
}
|
||||
|
||||
@ -83,6 +84,14 @@ func (in *ComponentUpgradePlan) DeepCopy() *ComponentUpgradePlan {
|
||||
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.
|
||||
func (in *Images) DeepCopyInto(out *Images) {
|
||||
*out = *in
|
||||
|
@ -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.
|
||||
func (in *ComponentUpgradePlan) DeepCopyInto(out *ComponentUpgradePlan) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
return
|
||||
}
|
||||
|
||||
@ -83,6 +84,14 @@ func (in *ComponentUpgradePlan) DeepCopy() *ComponentUpgradePlan {
|
||||
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.
|
||||
func (in *Images) DeepCopyInto(out *Images) {
|
||||
*out = *in
|
||||
|
@ -39,6 +39,7 @@ import (
|
||||
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
|
||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -332,7 +333,7 @@ func getInternalCfg(cfgPath string, kubeconfigPath string, cfg kubeadmapiv1.Clus
|
||||
if cfgPath == "" {
|
||||
client, err := kubeconfigutil.ClientSetFromFile(kubeconfigPath)
|
||||
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 {
|
||||
fmt.Println() // add empty line to separate the FetchInitConfigurationFromCluster output from the command output
|
||||
return internalcfg, nil
|
||||
|
@ -47,6 +47,7 @@ import (
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/discovery"
|
||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -618,7 +619,7 @@ func fetchInitConfiguration(tlsBootstrapCfg *clientcmdapi.Config) (*kubeadmapi.I
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap")
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import (
|
||||
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
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"
|
||||
)
|
||||
|
||||
@ -98,7 +99,7 @@ func newResetData(cmd *cobra.Command, options *resetOptions, in io.Reader, out i
|
||||
client, err := getClientset(options.kubeconfigPath, false)
|
||||
if err == nil {
|
||||
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 {
|
||||
klog.Warningf("[reset] Unable to fetch the kubeadm-config ConfigMap from cluster: %v", err)
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import (
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
|
||||
)
|
||||
|
||||
// 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)
|
||||
klog.V(1).Infoln("[upgrade/apply] verifying health of cluster")
|
||||
klog.V(1).Infoln("[upgrade/apply] retrieving configuration from cluster")
|
||||
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, flags.dryRun, true)
|
||||
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, flags.dryRun, true, &output.TextPrinter{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ import (
|
||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
|
||||
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
|
||||
@ -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
|
||||
// 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).
|
||||
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
|
||||
const logPrefix = "upgrade/config"
|
||||
|
||||
// The usual case here is to not have a config file, but rather load the config from the cluster.
|
||||
// This is probably 90% of the time. So we handle it first.
|
||||
if cfgPath == "" {
|
||||
cfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, logPrefix, false, skipComponentConfigs)
|
||||
cfg, err := configutil.FetchInitConfigurationFromCluster(client, printer, logPrefix, false, skipComponentConfigs)
|
||||
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.
|
||||
// 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 {
|
||||
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
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
fmt.Println("[upgrade/config] Making sure the configuration is correct:")
|
||||
printer.Printf("[upgrade/config] Making sure the configuration is correct:\n")
|
||||
|
||||
var newK8sVersion string
|
||||
cfg, legacyReconfigure, err := loadConfig(flags.cfgPath, client, !upgradeApply)
|
||||
cfg, legacyReconfigure, err := loadConfig(flags.cfgPath, client, !upgradeApply, printer)
|
||||
if err != nil {
|
||||
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)
|
||||
@ -161,7 +162,7 @@ func enforceRequirements(flags *applyPlanFlags, args []string, dryRun bool, upgr
|
||||
|
||||
// Ensure the user is root
|
||||
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
|
||||
}
|
||||
|
||||
@ -235,8 +236,8 @@ func printConfiguration(clustercfg *kubeadmapi.ClusterConfiguration, w io.Writer
|
||||
}
|
||||
|
||||
// runPreflightChecks runs the root preflight check
|
||||
func runPreflightChecks(client clientset.Interface, ignorePreflightErrors sets.String, cfg *kubeadmapi.ClusterConfiguration) error {
|
||||
fmt.Println("[preflight] Running pre-flight checks.")
|
||||
func runPreflightChecks(client clientset.Interface, ignorePreflightErrors sets.String, cfg *kubeadmapi.ClusterConfiguration, printer output.Printer) error {
|
||||
printer.Printf("[preflight] Running pre-flight checks.\n")
|
||||
err := preflight.RunRootCheckOnly(ignorePreflightErrors)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
|
||||
)
|
||||
|
||||
func TestEnforceRequirements(t *testing.T) {
|
||||
@ -54,7 +55,7 @@ func TestEnforceRequirements(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tcases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, _, _, err := enforceRequirements(&tt.flags, nil, tt.dryRun, false)
|
||||
_, _, _, err := enforceRequirements(&tt.flags, nil, tt.dryRun, false, &output.TextPrinter{})
|
||||
|
||||
if err == nil && tt.expectedErr {
|
||||
t.Error("Expected error, but got success")
|
||||
|
@ -37,6 +37,7 @@ import (
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
|
||||
)
|
||||
|
||||
type diffFlags struct {
|
||||
@ -118,7 +119,7 @@ func runDiff(flags *diffFlags, args []string) error {
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
|
@ -33,6 +33,7 @@ import (
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
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.
|
||||
@ -139,7 +140,7 @@ func newNodeData(cmd *cobra.Command, args []string, options *nodeOptions) (*node
|
||||
// Fetches the cluster configuration
|
||||
// NB in case of control-plane node, we are reading all the info for the node; in case of NOT control-plane node
|
||||
// (worker node), we are not reading local API address and the CRI socket from the node object
|
||||
cfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, "upgrade", !isControlPlaneNode, false)
|
||||
cfg, err := configutil.FetchInitConfigurationFromCluster(client, &output.TextPrinter{}, "upgrade", !isControlPlaneNode, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap")
|
||||
}
|
||||
|
@ -28,16 +28,21 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
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/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
|
||||
)
|
||||
|
||||
type planFlags struct {
|
||||
@ -50,25 +55,188 @@ func newCmdPlan(apf *applyPlanFlags) *cobra.Command {
|
||||
applyPlanFlags: apf,
|
||||
}
|
||||
|
||||
outputFlags := newUpgradePlanPrintFlags(output.TextOutput)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
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",
|
||||
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
|
||||
addApplyPlanFlags(cmd.Flags(), flags.applyPlanFlags)
|
||||
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
|
||||
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.
|
||||
klog.V(1).Infoln("[upgrade/plan] verifying health of cluster")
|
||||
klog.V(1).Infoln("[upgrade/plan] retrieving configuration from cluster")
|
||||
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, false, false)
|
||||
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, false, false, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -79,6 +247,7 @@ func runPlan(flags *planFlags, args []string) error {
|
||||
|
||||
// Compute which upgrade possibilities there are
|
||||
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())
|
||||
if err != nil {
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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 {
|
||||
@ -199,14 +361,14 @@ func getComponentConfigVersionStates(cfg *kubeadmapi.ClusterConfiguration, clien
|
||||
}
|
||||
|
||||
// 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
|
||||
tabw := tabwriter.NewWriter(w, 10, 4, 3, ' ', 0)
|
||||
|
||||
// endOfTable helper function flashes table writer
|
||||
endOfTable := func() {
|
||||
tabw.Flush()
|
||||
fmt.Fprintln(w, "")
|
||||
printer.Fprintln(w, "")
|
||||
}
|
||||
|
||||
printHeader := true
|
||||
@ -217,41 +379,41 @@ func printUpgradePlan(up *upgrade.Upgrade, plan *outputapi.UpgradePlan, unstable
|
||||
continue
|
||||
} else if component.Name == constants.Kubelet {
|
||||
if printManualUpgradeHeader {
|
||||
fmt.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")
|
||||
fmt.Fprintf(tabw, "%s\t%s\t%s\n", component.Name, component.CurrentVersion, component.NewVersion)
|
||||
printer.Fprintln(w, "Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':")
|
||||
printer.Fprintln(tabw, "COMPONENT\tCURRENT\tTARGET")
|
||||
printer.Fprintf(tabw, "%s\t%s\t%s\n", component.Name, component.CurrentVersion, component.NewVersion)
|
||||
printManualUpgradeHeader = false
|
||||
} 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 {
|
||||
if printHeader {
|
||||
// End of manual upgrades table
|
||||
endOfTable()
|
||||
|
||||
fmt.Fprintf(w, "Upgrade to the latest %s:\n", up.Description)
|
||||
fmt.Fprintln(w, "")
|
||||
fmt.Fprintln(tabw, "COMPONENT\tCURRENT\tTARGET")
|
||||
printer.Fprintf(w, "Upgrade to the latest %s:\n", up.Description)
|
||||
printer.Fprintln(w, "")
|
||||
printer.Fprintln(tabw, "COMPONENT\tCURRENT\tTARGET")
|
||||
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
|
||||
endOfTable()
|
||||
|
||||
//fmt.Fprintln(w, "")
|
||||
fmt.Fprintln(w, "You can now apply the upgrade by executing the following command:")
|
||||
fmt.Fprintln(w, "")
|
||||
fmt.Fprintf(w, "\tkubeadm upgrade apply %s%s\n", up.After.KubeVersion, unstableVersionFlag)
|
||||
fmt.Fprintln(w, "")
|
||||
printer.Fprintln(w, "You can now apply the upgrade by executing the following command:")
|
||||
printer.Fprintln(w, "")
|
||||
printer.Fprintf(w, "\tkubeadm upgrade apply %s%s\n", up.After.KubeVersion, unstableVersionFlag)
|
||||
printer.Fprintln(w, "")
|
||||
|
||||
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)
|
||||
fmt.Fprintln(w, "")
|
||||
printer.Fprintf(w, "Note: Before you can perform this upgrade, you have to update kubeadm to %s.\n", up.After.KubeadmVersion)
|
||||
printer.Fprintln(w, "")
|
||||
}
|
||||
|
||||
printLineSeparator(w)
|
||||
printLineSeparator(w, printer)
|
||||
}
|
||||
|
||||
// sortedSliceFromStringIntMap returns a slice of the keys in the map sorted alphabetically
|
||||
@ -278,18 +440,18 @@ func yesOrNo(b bool) string {
|
||||
return "no"
|
||||
}
|
||||
|
||||
func printLineSeparator(w io.Writer) {
|
||||
fmt.Fprintln(w, "_____________________________________________________________________")
|
||||
fmt.Fprintln(w, "")
|
||||
func printLineSeparator(w io.Writer, printer output.Printer) {
|
||||
printer.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 {
|
||||
fmt.Fprintln(w, "No information available on component configs.")
|
||||
printer.Fprintln(w, "No information available on component configs.")
|
||||
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.
|
||||
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
|
||||
@ -297,10 +459,10 @@ func printComponentConfigVersionStates(versionStates []outputapi.ComponentConfig
|
||||
`))
|
||||
|
||||
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 {
|
||||
fmt.Fprintf(tabw,
|
||||
printer.Fprintf(tabw,
|
||||
"%s\t%s\t%s\t%s\n",
|
||||
state.Group,
|
||||
strOrDash(state.CurrentVersion),
|
||||
@ -310,5 +472,5 @@ func printComponentConfigVersionStates(versionStates []outputapi.ComponentConfig
|
||||
}
|
||||
|
||||
tabw.Flush()
|
||||
printLineSeparator(w)
|
||||
printLineSeparator(w, printer)
|
||||
}
|
||||
|
@ -73,8 +73,6 @@ type ClusterState struct {
|
||||
// GetAvailableUpgrades fetches all versions from the specified VersionGetter and computes which
|
||||
// kinds of upgrades can be performed
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,6 @@ import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@ -42,12 +41,13 @@ import (
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
"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
|
||||
func FetchInitConfigurationFromCluster(client clientset.Interface, w io.Writer, logPrefix string, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) {
|
||||
fmt.Fprintf(w, "[%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)
|
||||
func FetchInitConfigurationFromCluster(client clientset.Interface, printer output.Printer, logPrefix string, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) {
|
||||
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
|
||||
cfg, err := getInitConfigurationFromCluster(constants.KubernetesDir, client, newControlPlane, skipComponentConfigs)
|
||||
|
@ -31,6 +31,12 @@ import (
|
||||
// TextOutput describes the plain text output
|
||||
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
|
||||
type TextPrintFlags interface {
|
||||
ToPrinter(outputFormat string) (Printer, error)
|
||||
@ -44,7 +50,7 @@ type PrintFlags struct {
|
||||
JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags
|
||||
// KubeTemplatePrintFlags composes print flags that provide both a JSONPath and a go-template printer.
|
||||
KubeTemplatePrintFlags *genericclioptions.KubeTemplatePrintFlags
|
||||
// JSONYamlPrintFlags provides default flags necessary for kubeadm text printing.
|
||||
// TextPrintFlags provides default flags necessary for kubeadm text printing.
|
||||
TextPrintFlags TextPrintFlags
|
||||
// TypeSetterPrinter is an implementation of ResourcePrinter that wraps another printer with types set on the objects
|
||||
TypeSetterPrinter *printers.TypeSetterPrinter
|
||||
@ -133,13 +139,27 @@ func NewOutputFlags(textPrintFlags TextPrintFlags) *PrintFlags {
|
||||
type Printer interface {
|
||||
PrintObj(obj runtime.Object, writer io.Writer) 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)
|
||||
|
||||
Flush(writer io.Writer)
|
||||
Close(writer io.Writer)
|
||||
}
|
||||
|
||||
// TextPrinter implements Printer interface for generic text output
|
||||
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
|
||||
func (tp *TextPrinter) PrintObj(obj runtime.Object, writer io.Writer) error {
|
||||
_, 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...)
|
||||
}
|
||||
|
||||
// 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
|
||||
func (tp *TextPrinter) Printf(format string, args ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, args...)
|
||||
@ -169,6 +194,16 @@ func NewResourcePrinterWrapper(resourcePrinter printers.ResourcePrinter, err err
|
||||
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
|
||||
func (rpw *ResourcePrinterWrapper) PrintObj(obj runtime.Object, writer io.Writer) error {
|
||||
return rpw.Printer.PrintObj(obj, writer)
|
||||
@ -181,6 +216,13 @@ func (rpw *ResourcePrinterWrapper) Fprintf(writer io.Writer, format string, args
|
||||
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
|
||||
// and silent info printing for structured output
|
||||
// This method is usually redefined for the text output
|
||||
|
Loading…
Reference in New Issue
Block a user