Merge pull request #127096 from neolit123/1.32-fix-unknown-phase-error

kubeadm: better error handling for unknown phases and commands
This commit is contained in:
Kubernetes Prow Robot 2024-09-10 11:00:48 +01:00 committed by GitHub
commit d913914511
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 28 additions and 56 deletions

View File

@ -104,9 +104,9 @@ func newCmdCertsUtility(out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "certs", Use: "certs",
Aliases: []string{"certificates"}, Aliases: []string{"certificates"},
Short: "Commands related to handling kubernetes certificates", Short: "Commands related to handling Kubernetes certificates",
Run: cmdutil.SubCmdRun(),
} }
cmdutil.RequireSubcommand(cmd)
cmd.AddCommand(newCmdCertsRenewal(out)) cmd.AddCommand(newCmdCertsRenewal(out))
cmd.AddCommand(newCmdCertsExpiration(out, kubeadmconstants.KubernetesDir)) cmd.AddCommand(newCmdCertsExpiration(out, kubeadmconstants.KubernetesDir))
@ -215,9 +215,8 @@ func newCmdCertsRenewal(out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "renew", Use: "renew",
Short: "Renew certificates for a Kubernetes cluster", Short: "Renew certificates for a Kubernetes cluster",
Long: cmdutil.MacroCommandLongDescription,
Run: cmdutil.SubCmdRun(),
} }
cmdutil.RequireSubcommand(cmd)
cmd.AddCommand(getRenewSubCommands(out, kubeadmconstants.KubernetesDir)...) cmd.AddCommand(getRenewSubCommands(out, kubeadmconstants.KubernetesDir)...)

View File

@ -62,13 +62,8 @@ func newCmdConfig(out io.Writer) *cobra.Command {
initialized your cluster using kubeadm v1.7.x or lower, you must use the 'kubeadm init phase upload-config' command to initialized your cluster using kubeadm v1.7.x or lower, you must use the 'kubeadm init phase upload-config' command to
create this ConfigMap. This is required so that 'kubeadm upgrade' can configure your upgraded cluster correctly. create this ConfigMap. This is required so that 'kubeadm upgrade' can configure your upgraded cluster correctly.
`), metav1.NamespaceSystem, constants.KubeadmConfigConfigMap), `), metav1.NamespaceSystem, constants.KubeadmConfigConfigMap),
// Without this callback, if a user runs just the "upload"
// command without a subcommand, or with an invalid subcommand,
// cobra will print usage information, but still exit cleanly.
// We want to return an error code in these cases so that the
// user knows that their command was invalid.
Run: cmdutil.SubCmdRun(),
} }
cmdutil.RequireSubcommand(cmd)
options.AddKubeConfigFlag(cmd.PersistentFlags(), &kubeConfigFile) options.AddKubeConfigFlag(cmd.PersistentFlags(), &kubeConfigFile)
@ -88,8 +83,8 @@ func newCmdConfigPrint(out io.Writer) *cobra.Command {
Long: dedent.Dedent(` Long: dedent.Dedent(`
This command prints configurations for subcommands provided. This command prints configurations for subcommands provided.
For details, see: https://pkg.go.dev/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm#section-directories`), For details, see: https://pkg.go.dev/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm#section-directories`),
Run: cmdutil.SubCmdRun(),
} }
cmdutil.RequireSubcommand(cmd)
cmd.AddCommand(newCmdConfigPrintInitDefaults(out)) cmd.AddCommand(newCmdConfigPrintInitDefaults(out))
cmd.AddCommand(newCmdConfigPrintJoinDefaults(out)) cmd.AddCommand(newCmdConfigPrintJoinDefaults(out))
cmd.AddCommand(newCmdConfigPrintResetDefaults(out)) cmd.AddCommand(newCmdConfigPrintResetDefaults(out))
@ -360,8 +355,8 @@ func newCmdConfigImages(out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "images", Use: "images",
Short: "Interact with container images used by kubeadm", Short: "Interact with container images used by kubeadm",
Run: cmdutil.SubCmdRun(),
} }
cmdutil.RequireSubcommand(cmd)
cmd.AddCommand(newCmdConfigImagesList(out, nil)) cmd.AddCommand(newCmdConfigImagesList(out, nil))
cmd.AddCommand(newCmdConfigImagesPull()) cmd.AddCommand(newCmdConfigImagesPull())
return cmd return cmd

View File

@ -56,7 +56,6 @@ func NewAddonPhase() workflow.Phase {
return workflow.Phase{ return workflow.Phase{
Name: "addon", Name: "addon",
Short: "Install required addons for passing conformance tests", Short: "Install required addons for passing conformance tests",
Long: cmdutil.MacroCommandLongDescription,
Phases: []workflow.Phase{ Phases: []workflow.Phase{
{ {
Name: "all", Name: "all",

View File

@ -57,7 +57,6 @@ func NewCertsPhase() workflow.Phase {
Short: "Certificate generation", Short: "Certificate generation",
Phases: newCertSubPhases(), Phases: newCertSubPhases(),
Run: runCerts, Run: runCerts,
Long: cmdutil.MacroCommandLongDescription,
} }
} }

View File

@ -66,7 +66,6 @@ func NewControlPlanePhase() workflow.Phase {
phase := workflow.Phase{ phase := workflow.Phase{
Name: "control-plane", Name: "control-plane",
Short: "Generate all static Pod manifest files necessary to establish the control plane", Short: "Generate all static Pod manifest files necessary to establish the control plane",
Long: cmdutil.MacroCommandLongDescription,
Phases: []workflow.Phase{ Phases: []workflow.Phase{
{ {
Name: "all", Name: "all",

View File

@ -47,7 +47,6 @@ func NewEtcdPhase() workflow.Phase {
phase := workflow.Phase{ phase := workflow.Phase{
Name: "etcd", Name: "etcd",
Short: "Generate static Pod manifest file for local etcd", Short: "Generate static Pod manifest file for local etcd",
Long: cmdutil.MacroCommandLongDescription,
Phases: []workflow.Phase{ Phases: []workflow.Phase{
newEtcdLocalSubPhase(), newEtcdLocalSubPhase(),
}, },

View File

@ -74,7 +74,6 @@ func NewKubeConfigPhase() workflow.Phase {
return workflow.Phase{ return workflow.Phase{
Name: "kubeconfig", Name: "kubeconfig",
Short: "Generate all kubeconfig files necessary to establish the control plane and the admin kubeconfig file", Short: "Generate all kubeconfig files necessary to establish the control plane and the admin kubeconfig file",
Long: cmdutil.MacroCommandLongDescription,
Phases: []workflow.Phase{ Phases: []workflow.Phase{
{ {
Name: "all", Name: "all",

View File

@ -65,7 +65,6 @@ func NewUploadConfigPhase() workflow.Phase {
Name: "upload-config", Name: "upload-config",
Aliases: []string{"uploadconfig"}, Aliases: []string{"uploadconfig"},
Short: "Upload the kubeadm and kubelet configuration to a ConfigMap", Short: "Upload the kubeadm and kubelet configuration to a ConfigMap",
Long: cmdutil.MacroCommandLongDescription,
Phases: []workflow.Phase{ Phases: []workflow.Phase{
{ {
Name: "all", Name: "all",

View File

@ -23,6 +23,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
) )
// phaseSeparator defines the separator to be used when concatenating nested // phaseSeparator defines the separator to be used when concatenating nested
@ -332,8 +334,9 @@ func (e *Runner) BindToCommand(cmd *cobra.Command) {
// adds the phases subcommand // adds the phases subcommand
phaseCommand := &cobra.Command{ phaseCommand := &cobra.Command{
Use: "phase", Use: "phase",
Short: fmt.Sprintf("Use this command to invoke single phase of the %s workflow", cmd.Name()), Short: fmt.Sprintf("Use this command to invoke single phase of the %q workflow", cmd.Name()),
} }
cmdutil.RequireSubcommand(phaseCommand)
cmd.AddCommand(phaseCommand) cmd.AddCommand(phaseCommand)
@ -363,7 +366,8 @@ func (e *Runner) BindToCommand(cmd *cobra.Command) {
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
// if the phase has subphases, print the help and exits // if the phase has subphases, print the help and exits
if len(p.Phases) > 0 { if len(p.Phases) > 0 {
return cmd.Help() _ = cmd.Help()
return cmdutil.ErrorSubcommandRequired
} }
// overrides the command triggering the Runner using the phaseCmd // overrides the command triggering the Runner using the phaseCmd

View File

@ -77,14 +77,8 @@ func newCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
You can read more about bootstrap tokens here: You can read more about bootstrap tokens here:
https://kubernetes.io/docs/admin/bootstrap-tokens/ https://kubernetes.io/docs/admin/bootstrap-tokens/
`), `),
// Without this callback, if a user runs just the "token"
// command without a subcommand, or with an invalid subcommand,
// cobra will print usage information, but still exit cleanly.
// We want to return an error code in these cases so that the
// user knows that their command was invalid.
Run: cmdutil.SubCmdRun(),
} }
cmdutil.RequireSubcommand(tokenCmd)
options.AddKubeConfigFlag(tokenCmd.PersistentFlags(), &kubeConfigFile) options.AddKubeConfigFlag(tokenCmd.PersistentFlags(), &kubeConfigFile)
tokenCmd.PersistentFlags().BoolVar(&dryRun, tokenCmd.PersistentFlags().BoolVar(&dryRun,

View File

@ -56,8 +56,8 @@ func NewCmdUpgrade(out io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "upgrade", Use: "upgrade",
Short: "Upgrade your cluster smoothly to a newer version with this command", Short: "Upgrade your cluster smoothly to a newer version with this command",
Run: cmdutil.SubCmdRun(),
} }
cmdutil.RequireSubcommand(cmd)
cmd.AddCommand(newCmdApply(flags)) cmd.AddCommand(newCmdApply(flags))
cmd.AddCommand(newCmdPlan(flags)) cmd.AddCommand(newCmdPlan(flags))

View File

@ -34,27 +34,23 @@ import (
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
) )
// SubCmdRun returns a function that handles a case where a subcommand must be specified // ErrorSubcommandRequired is an error returned when a parent command cannot be executed.
// Without this callback, if a user runs just the command without a subcommand, // It starts with a new line so that it's separated from previous information like a Help() screen.
// or with an invalid subcommand, cobra will print usage information, but still exit cleanly. var ErrorSubcommandRequired = errors.New("\nerror: subcommand is required")
func SubCmdRun() func(c *cobra.Command, args []string) {
return func(c *cobra.Command, args []string) {
if len(args) > 0 {
kubeadmutil.CheckErr(usageErrorf(c, "invalid subcommand %q", strings.Join(args, " ")))
}
c.Help()
kubeadmutil.CheckErr(kubeadmutil.ErrExit)
}
}
func usageErrorf(c *cobra.Command, format string, args ...interface{}) error { // RequireSubcommand can be used to set an empty Run function and NoArgs on a Cobra command.
msg := fmt.Sprintf(format, args...) // This handles a case where a subcommand must be specified for a parent command 'c'.
return errors.Errorf("%s\nSee '%s -h' for help and examples", msg, c.CommandPath()) // If no subcommand is specified the CLI exist with an error.
func RequireSubcommand(c *cobra.Command) {
c.RunE = func(c *cobra.Command, args []string) error {
_ = c.Help()
return ErrorSubcommandRequired
}
c.Args = cobra.NoArgs
} }
// ValidateExactArgNumber validates that the required top-level arguments are specified // ValidateExactArgNumber validates that the required top-level arguments are specified

View File

@ -25,11 +25,6 @@ var (
AlphaDisclaimer = ` AlphaDisclaimer = `
Alpha Disclaimer: this command is currently alpha. Alpha Disclaimer: this command is currently alpha.
` `
// MacroCommandLongDescription provide a standard description for "macro" commands
MacroCommandLongDescription = LongDesc(`
This command is not meant to be run on its own. See list of available subcommands.
`)
) )
// LongDesc is designed to help with producing better long command line descriptions in code. // LongDesc is designed to help with producing better long command line descriptions in code.

View File

@ -38,8 +38,6 @@ const (
) )
var ( var (
// ErrInvalidSubCommandMsg is an error message returned on invalid subcommands
ErrInvalidSubCommandMsg = "invalid subcommand"
// ErrExit is an error returned when kubeadm is about to exit // ErrExit is an error returned when kubeadm is about to exit
ErrExit = errors.New("exit") ErrExit = errors.New("exit")
) )
@ -80,10 +78,10 @@ func checkErr(err error, handleErr func(string, int)) {
} }
msg := fmt.Sprintf("%s\nTo see the stack trace of this error execute with --v=5 or higher", err.Error()) msg := fmt.Sprintf("%s\nTo see the stack trace of this error execute with --v=5 or higher", err.Error())
// check if the verbosity level in klog is high enough and print a stack trace. // Check if the verbosity level in klog is high enough and print a stack trace.
f := flag.CommandLine.Lookup("v") f := flag.CommandLine.Lookup("v")
if f != nil { if f != nil {
// assume that the "v" flag contains a parseable Int32 as per klog's "Level" type alias, // Assume that the "v" flag contains a parsable Int32 as per klog's "Level" type alias,
// thus no error from ParseInt is handled here. // thus no error from ParseInt is handled here.
if v, e := strconv.ParseInt(f.Value.String(), 10, 32); e == nil { if v, e := strconv.ParseInt(f.Value.String(), 10, 32); e == nil {
// https://git.k8s.io/community/contributors/devel/sig-instrumentation/logging.md // https://git.k8s.io/community/contributors/devel/sig-instrumentation/logging.md
@ -97,15 +95,12 @@ func checkErr(err error, handleErr func(string, int)) {
switch { switch {
case err == ErrExit: case err == ErrExit:
handleErr("", DefaultErrorExitCode) handleErr("", DefaultErrorExitCode)
case strings.Contains(err.Error(), ErrInvalidSubCommandMsg):
handleErr(err.Error(), DefaultErrorExitCode)
default: default:
switch err.(type) { switch err.(type) {
case preflightError: case preflightError:
handleErr(msg, PreFlightExitCode) handleErr(msg, PreFlightExitCode)
case errorsutil.Aggregate: case errorsutil.Aggregate:
handleErr(msg, ValidationExitCode) handleErr(msg, ValidationExitCode)
default: default:
handleErr(msg, DefaultErrorExitCode) handleErr(msg, DefaultErrorExitCode)
} }