Merge pull request #111277 from chymy/improve-kubeadm-subcommand-tips

kubeadm: improve tips of incorrect input of kubedm subcommand
This commit is contained in:
Kubernetes Prow Robot 2022-09-09 00:49:23 -07:00 committed by GitHub
commit 2b2be7fa6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 57 additions and 37 deletions

View File

@ -96,6 +96,7 @@ func newCmdCertsUtility(out io.Writer) *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(),
} }
cmd.AddCommand(newCmdCertsRenewal(out)) cmd.AddCommand(newCmdCertsRenewal(out))
@ -203,7 +204,7 @@ func newCmdCertsRenewal(out io.Writer) *cobra.Command {
Use: "renew", Use: "renew",
Short: "Renew certificates for a Kubernetes cluster", Short: "Renew certificates for a Kubernetes cluster",
Long: cmdutil.MacroCommandLongDescription, Long: cmdutil.MacroCommandLongDescription,
RunE: cmdutil.SubCmdRunE("renew"), Run: cmdutil.SubCmdRun(),
} }
cmd.AddCommand(getRenewSubCommands(out, kubeadmconstants.KubernetesDir)...) cmd.AddCommand(getRenewSubCommands(out, kubeadmconstants.KubernetesDir)...)

View File

@ -68,7 +68,7 @@ func newCmdConfig(out io.Writer) *cobra.Command {
// cobra will print usage information, but still exit cleanly. // cobra will print usage information, but still exit cleanly.
// We want to return an error code in these cases so that the // We want to return an error code in these cases so that the
// user knows that their command was invalid. // user knows that their command was invalid.
RunE: cmdutil.SubCmdRunE("config"), Run: cmdutil.SubCmdRun(),
} }
options.AddKubeConfigFlag(cmd.PersistentFlags(), &kubeConfigFile) options.AddKubeConfigFlag(cmd.PersistentFlags(), &kubeConfigFile)
@ -88,7 +88,7 @@ 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`),
RunE: cmdutil.SubCmdRunE("print"), Run: cmdutil.SubCmdRun(),
} }
cmd.AddCommand(newCmdConfigPrintInitDefaults(out)) cmd.AddCommand(newCmdConfigPrintInitDefaults(out))
cmd.AddCommand(newCmdConfigPrintJoinDefaults(out)) cmd.AddCommand(newCmdConfigPrintJoinDefaults(out))
@ -277,7 +277,7 @@ 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",
RunE: cmdutil.SubCmdRunE("images"), Run: cmdutil.SubCmdRun(),
} }
cmd.AddCommand(newCmdConfigImagesList(out, nil)) cmd.AddCommand(newCmdConfigImagesList(out, nil))
cmd.AddCommand(newCmdConfigImagesPull()) cmd.AddCommand(newCmdConfigImagesPull())

View File

@ -98,7 +98,7 @@ func newResetOptions() *resetOptions {
func newResetData(cmd *cobra.Command, options *resetOptions, in io.Reader, out io.Writer) (*resetData, error) { func newResetData(cmd *cobra.Command, options *resetOptions, in io.Reader, out io.Writer) (*resetData, error) {
var cfg *kubeadmapi.InitConfiguration var cfg *kubeadmapi.InitConfiguration
client, err := cmdutil.GetClientset(options.kubeconfigPath, false) client, err := cmdutil.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, nil, "reset", false, false) cfg, err = configutil.FetchInitConfigurationFromCluster(client, nil, "reset", false, false)

View File

@ -83,7 +83,7 @@ func newCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
// cobra will print usage information, but still exit cleanly. // cobra will print usage information, but still exit cleanly.
// We want to return an error code in these cases so that the // We want to return an error code in these cases so that the
// user knows that their command was invalid. // user knows that their command was invalid.
RunE: cmdutil.SubCmdRunE("token"), Run: cmdutil.SubCmdRun(),
} }
options.AddKubeConfigFlag(tokenCmd.PersistentFlags(), &kubeConfigFile) options.AddKubeConfigFlag(tokenCmd.PersistentFlags(), &kubeConfigFile)
@ -127,7 +127,7 @@ func newCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
klog.V(1).Infoln("[token] getting Clientsets from kubeconfig file") klog.V(1).Infoln("[token] getting Clientsets from kubeconfig file")
kubeConfigFile = cmdutil.GetKubeConfigPath(kubeConfigFile) kubeConfigFile = cmdutil.GetKubeConfigPath(kubeConfigFile)
client, err := cmdutil.GetClientset(kubeConfigFile, dryRun) client, err := cmdutil.GetClientSet(kubeConfigFile, dryRun)
if err != nil { if err != nil {
return err return err
} }
@ -159,7 +159,7 @@ func newCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
`), `),
RunE: func(tokenCmd *cobra.Command, args []string) error { RunE: func(tokenCmd *cobra.Command, args []string) error {
kubeConfigFile = cmdutil.GetKubeConfigPath(kubeConfigFile) kubeConfigFile = cmdutil.GetKubeConfigPath(kubeConfigFile)
client, err := cmdutil.GetClientset(kubeConfigFile, dryRun) client, err := cmdutil.GetClientSet(kubeConfigFile, dryRun)
if err != nil { if err != nil {
return err return err
} }
@ -193,7 +193,7 @@ func newCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
return errors.Errorf("missing subcommand; 'token delete' is missing token of form %q", bootstrapapi.BootstrapTokenIDPattern) return errors.Errorf("missing subcommand; 'token delete' is missing token of form %q", bootstrapapi.BootstrapTokenIDPattern)
} }
kubeConfigFile = cmdutil.GetKubeConfigPath(kubeConfigFile) kubeConfigFile = cmdutil.GetKubeConfigPath(kubeConfigFile)
client, err := cmdutil.GetClientset(kubeConfigFile, dryRun) client, err := cmdutil.GetClientSet(kubeConfigFile, dryRun)
if err != nil { if err != nil {
return err return err
} }

View File

@ -265,7 +265,7 @@ func TestNewCmdToken(t *testing.T) {
} }
} }
func TestGetClientset(t *testing.T) { func TestGetClientSet(t *testing.T) {
testConfigTokenFile := "test-config-file" testConfigTokenFile := "test-config-file"
tmpDir, err := os.MkdirTemp("", "kubeadm-token-test") tmpDir, err := os.MkdirTemp("", "kubeadm-token-test")
@ -276,13 +276,13 @@ func TestGetClientset(t *testing.T) {
fullPath := filepath.Join(tmpDir, testConfigTokenFile) fullPath := filepath.Join(tmpDir, testConfigTokenFile)
// test dryRun = false on a non-exisiting file // test dryRun = false on a non-exisiting file
if _, err = cmdutil.GetClientset(fullPath, false); err == nil { if _, err = cmdutil.GetClientSet(fullPath, false); err == nil {
t.Errorf("getClientset(); dry-run: false; did no fail for test file %q: %v", fullPath, err) t.Errorf("GetClientSet(); dry-run: false; did no fail for test file %q: %v", fullPath, err)
} }
// test dryRun = true on a non-exisiting file // test dryRun = true on a non-exisiting file
if _, err = cmdutil.GetClientset(fullPath, true); err == nil { if _, err = cmdutil.GetClientSet(fullPath, true); err == nil {
t.Errorf("getClientset(); dry-run: true; did no fail for test file %q: %v", fullPath, err) t.Errorf("GetClientSet(); dry-run: true; did no fail for test file %q: %v", fullPath, err)
} }
f, err := os.Create(fullPath) f, err := os.Create(fullPath)
@ -296,8 +296,8 @@ func TestGetClientset(t *testing.T) {
} }
// test dryRun = true on an exisiting file // test dryRun = true on an exisiting file
if _, err = cmdutil.GetClientset(fullPath, true); err != nil { if _, err = cmdutil.GetClientSet(fullPath, true); err != nil {
t.Errorf("getClientset(); dry-run: true; failed for test file %q: %v", fullPath, err) t.Errorf("GetClientSet(); dry-run: true; failed for test file %q: %v", fullPath, err)
} }
} }
@ -321,9 +321,9 @@ func TestRunDeleteTokens(t *testing.T) {
t.Errorf("Unable to write test file %q: %v", fullPath, err) t.Errorf("Unable to write test file %q: %v", fullPath, err)
} }
client, err := cmdutil.GetClientset(fullPath, true) client, err := cmdutil.GetClientSet(fullPath, true)
if err != nil { if err != nil {
t.Errorf("Unable to run getClientset() for test file %q: %v", fullPath, err) t.Errorf("Unable to run GetClientSet() for test file %q: %v", fullPath, err)
} }
// test valid; should not fail // test valid; should not fail

View File

@ -54,7 +54,7 @@ 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",
RunE: cmdutil.SubCmdRunE("upgrade"), Run: cmdutil.SubCmdRun(),
} }
cmd.AddCommand(newCmdApply(flags)) cmd.AddCommand(newCmdApply(flags))

View File

@ -34,25 +34,29 @@ import (
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/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"
) )
// SubCmdRunE returns a function that handles a case where a subcommand must be specified // SubCmdRun returns a function that handles a case where a subcommand must be specified
// Without this callback, if a user runs just the command without a subcommand, // Without this callback, if a user runs just the command without a subcommand,
// or with an invalid subcommand, cobra will print usage information, but still exit cleanly. // 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 func SubCmdRun() func(c *cobra.Command, args []string) {
// user knows that their command was invalid. return func(c *cobra.Command, args []string) {
func SubCmdRunE(name string) func(*cobra.Command, []string) error { if len(args) > 0 {
return func(_ *cobra.Command, args []string) error { kubeadmutil.CheckErr(usageErrorf(c, "invalid subcommand %q", strings.Join(args, " ")))
if len(args) < 1 {
return errors.Errorf("missing subcommand; %q is not meant to be run on its own", name)
} }
c.Help()
return errors.Errorf("invalid subcommand: %q", args[0]) kubeadmutil.CheckErr(kubeadmutil.ErrExit)
} }
} }
func usageErrorf(c *cobra.Command, format string, args ...interface{}) error {
msg := fmt.Sprintf(format, args...)
return errors.Errorf("%s\nSee '%s -h' for help and examples", msg, c.CommandPath())
}
// ValidateExactArgNumber validates that the required top-level arguments are specified // ValidateExactArgNumber validates that the required top-level arguments are specified
func ValidateExactArgNumber(args []string, supportedArgs []string) error { func ValidateExactArgNumber(args []string, supportedArgs []string) error {
lenSupported := len(supportedArgs) lenSupported := len(supportedArgs)
@ -127,7 +131,7 @@ func InteractivelyConfirmAction(action, question string, r io.Reader) error {
} }
// GetClientSet gets a real or fake client depending on whether the user is dry-running or not // GetClientSet gets a real or fake client depending on whether the user is dry-running or not
func GetClientset(file string, dryRun bool) (clientset.Interface, error) { func GetClientSet(file string, dryRun bool) (clientset.Interface, error) {
if dryRun { if dryRun {
dryRunGetter, err := apiclient.NewClientBackedDryRunGetterFromKubeconfig(file) dryRunGetter, err := apiclient.NewClientBackedDryRunGetterFromKubeconfig(file)
if err != nil { if err != nil {

View File

@ -23,6 +23,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/pkg/errors"
errorsutil "k8s.io/apimachinery/pkg/util/errors" errorsutil "k8s.io/apimachinery/pkg/util/errors"
) )
@ -35,6 +37,11 @@ const (
ValidationExitCode = 3 ValidationExitCode = 3
) )
var (
ErrInvalidSubCommandMsg = "invalid subcommand"
ErrExit = errors.New("exit")
)
// fatal prints the message if set and then exits. // fatal prints the message if set and then exits.
func fatal(msg string, code int) { func fatal(msg string, code int) {
if len(msg) > 0 { if len(msg) > 0 {
@ -85,16 +92,24 @@ func checkErr(err error, handleErr func(string, int)) {
} }
} }
switch err.(type) { if err == nil {
case nil:
return return
case preflightError: }
handleErr(msg, PreFlightExitCode) switch {
case errorsutil.Aggregate: case err == ErrExit:
handleErr(msg, ValidationExitCode) handleErr("", DefaultErrorExitCode)
case strings.Contains(err.Error(), ErrInvalidSubCommandMsg):
handleErr(err.Error(), DefaultErrorExitCode)
default: default:
handleErr(msg, DefaultErrorExitCode) switch err.(type) {
case preflightError:
handleErr(msg, PreFlightExitCode)
case errorsutil.Aggregate:
handleErr(msg, ValidationExitCode)
default:
handleErr(msg, DefaultErrorExitCode)
}
} }
} }