From 90653b39b263b7676f7493c043eb45a71b0a9420 Mon Sep 17 00:00:00 2001 From: hwdef Date: Sat, 28 Jun 2025 16:58:41 +0000 Subject: [PATCH] etcdctl: organize etcdctl subcommand & delete template Signed-off-by: hwdef --- etcdctl/ctlv3/command/alarm_command.go | 6 +- etcdctl/ctlv3/command/auth_command.go | 6 +- etcdctl/ctlv3/command/check.go | 6 +- etcdctl/ctlv3/command/compaction_command.go | 7 +- etcdctl/ctlv3/command/completion_command.go | 1 + etcdctl/ctlv3/command/defrag_command.go | 7 +- etcdctl/ctlv3/command/del_command.go | 7 +- etcdctl/ctlv3/command/downgrade_command.go | 6 +- etcdctl/ctlv3/command/elect_command.go | 7 +- etcdctl/ctlv3/command/ep_command.go | 6 +- etcdctl/ctlv3/command/get_command.go | 7 +- etcdctl/ctlv3/command/groups.go | 60 +++++++ etcdctl/ctlv3/command/help_command.go | 21 +++ etcdctl/ctlv3/command/lease_command.go | 6 +- etcdctl/ctlv3/command/lock_command.go | 7 +- etcdctl/ctlv3/command/make_mirror_command.go | 7 +- etcdctl/ctlv3/command/member_command.go | 6 +- etcdctl/ctlv3/command/move_leader_command.go | 7 +- etcdctl/ctlv3/command/put_command.go | 3 +- etcdctl/ctlv3/command/role_command.go | 6 +- etcdctl/ctlv3/command/snapshot_command.go | 1 + etcdctl/ctlv3/command/txn_command.go | 6 +- etcdctl/ctlv3/command/user_command.go | 6 +- etcdctl/ctlv3/command/version_command.go | 7 +- etcdctl/ctlv3/command/watch_command.go | 7 +- etcdctl/ctlv3/ctl.go | 17 +- etcdctl/util/help.go | 180 ------------------- 27 files changed, 175 insertions(+), 238 deletions(-) create mode 100644 etcdctl/ctlv3/command/groups.go create mode 100644 etcdctl/ctlv3/command/help_command.go delete mode 100644 etcdctl/util/help.go diff --git a/etcdctl/ctlv3/command/alarm_command.go b/etcdctl/ctlv3/command/alarm_command.go index 679f9d98f..9bf6ffbda 100644 --- a/etcdctl/ctlv3/command/alarm_command.go +++ b/etcdctl/ctlv3/command/alarm_command.go @@ -26,8 +26,10 @@ import ( // NewAlarmCommand returns the cobra command for "alarm". func NewAlarmCommand() *cobra.Command { ac := &cobra.Command{ - Use: "alarm ", - Short: "Alarm related commands", + Use: "alarm ", + Short: "Alarm related commands. Use `etcdctl alarm --help` to see subcommands", + Long: "Alarm related commands", + GroupID: groupClusterMaintenanceID, } ac.AddCommand(NewAlarmDisarmCommand()) diff --git a/etcdctl/ctlv3/command/auth_command.go b/etcdctl/ctlv3/command/auth_command.go index 13ccda44d..6a414f7aa 100644 --- a/etcdctl/ctlv3/command/auth_command.go +++ b/etcdctl/ctlv3/command/auth_command.go @@ -27,8 +27,10 @@ import ( // NewAuthCommand returns the cobra command for "auth". func NewAuthCommand() *cobra.Command { ac := &cobra.Command{ - Use: "auth ", - Short: "Enable or disable authentication", + Use: "auth ", + Short: "Enable or disable authentication. Use `etcdctl auth --help` to see subcommands", + Long: "Enable or disable authentication", + GroupID: groupAuthenticationID, } ac.AddCommand(newAuthEnableCommand()) diff --git a/etcdctl/ctlv3/command/check.go b/etcdctl/ctlv3/command/check.go index c88015400..904120982 100644 --- a/etcdctl/ctlv3/command/check.go +++ b/etcdctl/ctlv3/command/check.go @@ -107,8 +107,10 @@ var checkDatascaleCfgMap = map[string]checkDatascaleCfg{ // NewCheckCommand returns the cobra command for "check". func NewCheckCommand() *cobra.Command { cc := &cobra.Command{ - Use: "check ", - Short: "commands for checking properties of the etcd cluster", + Use: "check ", + Short: "commands for checking properties of the etcd cluster. Use `etcdctl check --help` to see subcommands", + Long: "commands for checking properties of the etcd cluster", + GroupID: groupUtilityID, } cc.AddCommand(NewCheckPerfCommand()) diff --git a/etcdctl/ctlv3/command/compaction_command.go b/etcdctl/ctlv3/command/compaction_command.go index 5c0bb1019..457538a59 100644 --- a/etcdctl/ctlv3/command/compaction_command.go +++ b/etcdctl/ctlv3/command/compaction_command.go @@ -29,9 +29,10 @@ var compactPhysical bool // NewCompactionCommand returns the cobra command for "compaction". func NewCompactionCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "compaction [options] ", - Short: "Compacts the event history in etcd", - Run: compactionCommandFunc, + Use: "compaction [options] ", + Short: "Compacts the event history in etcd", + Run: compactionCommandFunc, + GroupID: groupKVID, } cmd.Flags().BoolVar(&compactPhysical, "physical", false, "'true' to wait for compaction to physically remove all old revisions") return cmd diff --git a/etcdctl/ctlv3/command/completion_command.go b/etcdctl/ctlv3/command/completion_command.go index cb98d4a7d..45f284426 100644 --- a/etcdctl/ctlv3/command/completion_command.go +++ b/etcdctl/ctlv3/command/completion_command.go @@ -78,6 +78,7 @@ PowerShell: cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) } }, + GroupID: groupUtilityID, } return cmd diff --git a/etcdctl/ctlv3/command/defrag_command.go b/etcdctl/ctlv3/command/defrag_command.go index 36116d488..c68de9cd1 100644 --- a/etcdctl/ctlv3/command/defrag_command.go +++ b/etcdctl/ctlv3/command/defrag_command.go @@ -27,9 +27,10 @@ import ( // NewDefragCommand returns the cobra command for "Defrag". func NewDefragCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "defrag", - Short: "Defragments the storage of the etcd members with given endpoints", - Run: defragCommandFunc, + Use: "defrag", + Short: "Defragments the storage of the etcd members with given endpoints", + Run: defragCommandFunc, + GroupID: groupClusterMaintenanceID, } cmd.PersistentFlags().BoolVar(&epClusterEndpoints, "cluster", false, "use all endpoints from the cluster member list") return cmd diff --git a/etcdctl/ctlv3/command/del_command.go b/etcdctl/ctlv3/command/del_command.go index 51b7abb3e..5fa39f807 100644 --- a/etcdctl/ctlv3/command/del_command.go +++ b/etcdctl/ctlv3/command/del_command.go @@ -35,9 +35,10 @@ var ( // NewDelCommand returns the cobra command for "del". func NewDelCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "del [options] [range_end]", - Short: "Removes the specified key or range of keys [key, range_end)", - Run: delCommandFunc, + Use: "del [options] [range_end]", + Short: "Removes the specified key or range of keys [key, range_end)", + Run: delCommandFunc, + GroupID: groupKVID, } cmd.Flags().BoolVar(&delPrefix, "prefix", false, "delete keys with matching prefix") diff --git a/etcdctl/ctlv3/command/downgrade_command.go b/etcdctl/ctlv3/command/downgrade_command.go index 8b6ab9cd1..c56b7749c 100644 --- a/etcdctl/ctlv3/command/downgrade_command.go +++ b/etcdctl/ctlv3/command/downgrade_command.go @@ -26,8 +26,10 @@ import ( // NewDowngradeCommand returns the cobra command for "downgrade". func NewDowngradeCommand() *cobra.Command { dc := &cobra.Command{ - Use: "downgrade ", - Short: "Downgrade related commands", + Use: "downgrade ", + Short: "Downgrade related commands. Use `etcdctl downgrade --help` to see subcommands", + Long: "Downgrade related commands", + GroupID: groupClusterMaintenanceID, } dc.AddCommand(NewDowngradeValidateCommand()) diff --git a/etcdctl/ctlv3/command/elect_command.go b/etcdctl/ctlv3/command/elect_command.go index eee4289e7..6ff828843 100644 --- a/etcdctl/ctlv3/command/elect_command.go +++ b/etcdctl/ctlv3/command/elect_command.go @@ -33,9 +33,10 @@ var electListen bool // NewElectCommand returns the cobra command for "elect". func NewElectCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "elect [proposal]", - Short: "Observes and participates in leader election", - Run: electCommandFunc, + Use: "elect [proposal]", + Short: "Observes and participates in leader election", + Run: electCommandFunc, + GroupID: groupConcurrencyID, } cmd.Flags().BoolVarP(&electListen, "listen", "l", false, "observation mode") return cmd diff --git a/etcdctl/ctlv3/command/ep_command.go b/etcdctl/ctlv3/command/ep_command.go index d82194d32..e074a6218 100644 --- a/etcdctl/ctlv3/command/ep_command.go +++ b/etcdctl/ctlv3/command/ep_command.go @@ -39,8 +39,10 @@ var ( // NewEndpointCommand returns the cobra command for "endpoint". func NewEndpointCommand() *cobra.Command { ec := &cobra.Command{ - Use: "endpoint ", - Short: "Endpoint related commands", + Use: "endpoint ", + Short: "Endpoint related commands. Use `etcdctl endpoint --help` to see subcommands", + Long: "Endpoint related commands", + GroupID: groupClusterMaintenanceID, } ec.PersistentFlags().BoolVar(&epClusterEndpoints, "cluster", false, "use all endpoints from the cluster member list") diff --git a/etcdctl/ctlv3/command/get_command.go b/etcdctl/ctlv3/command/get_command.go index 7d687aa5c..94d34dac0 100644 --- a/etcdctl/ctlv3/command/get_command.go +++ b/etcdctl/ctlv3/command/get_command.go @@ -44,9 +44,10 @@ var ( // NewGetCommand returns the cobra command for "get". func NewGetCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "get [options] [range_end]", - Short: "Gets the key or a range of keys", - Run: getCommandFunc, + Use: "get [options] [range_end]", + Short: "Gets the key or a range of keys", + Run: getCommandFunc, + GroupID: groupKVID, } cmd.Flags().StringVar(&getConsistency, "consistency", "l", "Linearizable(l) or Serializable(s)") diff --git a/etcdctl/ctlv3/command/groups.go b/etcdctl/ctlv3/command/groups.go new file mode 100644 index 000000000..ca0229d3c --- /dev/null +++ b/etcdctl/ctlv3/command/groups.go @@ -0,0 +1,60 @@ +// Copyright 2025 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package command + +import "github.com/spf13/cobra" + +const ( + groupKVID = "kv" + groupClusterMaintenanceID = "cluster maintenance" + groupConcurrencyID = "concurrency" + groupAuthenticationID = "authentication" + groupUtilityID = "utility" +) + +func NewKVGroup() *cobra.Group { + return &cobra.Group{ + ID: groupKVID, + Title: "Key-value commands", + } +} + +func NewClusterMaintenanceGroup() *cobra.Group { + return &cobra.Group{ + ID: groupClusterMaintenanceID, + Title: "Cluster maintenance commands", + } +} + +func NewConcurrencyGroup() *cobra.Group { + return &cobra.Group{ + ID: groupConcurrencyID, + Title: "Concurrency commands", + } +} + +func NewAuthenticationGroup() *cobra.Group { + return &cobra.Group{ + ID: groupAuthenticationID, + Title: "Authentication commands", + } +} + +func NewUtilityGroup() *cobra.Group { + return &cobra.Group{ + ID: groupUtilityID, + Title: "Utility commands", + } +} diff --git a/etcdctl/ctlv3/command/help_command.go b/etcdctl/ctlv3/command/help_command.go new file mode 100644 index 000000000..259069791 --- /dev/null +++ b/etcdctl/ctlv3/command/help_command.go @@ -0,0 +1,21 @@ +// Copyright 2025 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package command + +import "github.com/spf13/cobra" + +func SetHelpCmdGroup(rootCmd *cobra.Command) { + rootCmd.SetHelpCommandGroupID(groupUtilityID) +} diff --git a/etcdctl/ctlv3/command/lease_command.go b/etcdctl/ctlv3/command/lease_command.go index 8e70670b6..0fccc5f64 100644 --- a/etcdctl/ctlv3/command/lease_command.go +++ b/etcdctl/ctlv3/command/lease_command.go @@ -28,8 +28,10 @@ import ( // NewLeaseCommand returns the cobra command for "lease". func NewLeaseCommand() *cobra.Command { lc := &cobra.Command{ - Use: "lease ", - Short: "Lease related commands", + Use: "lease ", + Short: "Lease related commands. Use `etcdctl lease --help` to see subcommands", + Long: "Lease related commands", + GroupID: groupKVID, } lc.AddCommand(NewLeaseGrantCommand()) diff --git a/etcdctl/ctlv3/command/lock_command.go b/etcdctl/ctlv3/command/lock_command.go index 64fb0ee81..0faa76fe6 100644 --- a/etcdctl/ctlv3/command/lock_command.go +++ b/etcdctl/ctlv3/command/lock_command.go @@ -35,9 +35,10 @@ var lockTTL = 10 // NewLockCommand returns the cobra command for "lock". func NewLockCommand() *cobra.Command { c := &cobra.Command{ - Use: "lock [exec-command arg1 arg2 ...]", - Short: "Acquires a named lock", - Run: lockCommandFunc, + Use: "lock [exec-command arg1 arg2 ...]", + Short: "Acquires a named lock", + Run: lockCommandFunc, + GroupID: groupConcurrencyID, } c.Flags().IntVarP(&lockTTL, "ttl", "", lockTTL, "timeout for session") return c diff --git a/etcdctl/ctlv3/command/make_mirror_command.go b/etcdctl/ctlv3/command/make_mirror_command.go index c8b6220d5..eea3d293e 100644 --- a/etcdctl/ctlv3/command/make_mirror_command.go +++ b/etcdctl/ctlv3/command/make_mirror_command.go @@ -53,9 +53,10 @@ var ( // NewMakeMirrorCommand returns the cobra command for "makeMirror". func NewMakeMirrorCommand() *cobra.Command { c := &cobra.Command{ - Use: "make-mirror [options] ", - Short: "Makes a mirror at the destination etcd cluster", - Run: makeMirrorCommandFunc, + Use: "make-mirror [options] ", + Short: "Makes a mirror at the destination etcd cluster", + Run: makeMirrorCommandFunc, + GroupID: groupUtilityID, } c.Flags().StringVar(&mmprefix, "prefix", "", "Key-value prefix to mirror") diff --git a/etcdctl/ctlv3/command/member_command.go b/etcdctl/ctlv3/command/member_command.go index 256bf2b9c..0ead08f43 100644 --- a/etcdctl/ctlv3/command/member_command.go +++ b/etcdctl/ctlv3/command/member_command.go @@ -35,8 +35,10 @@ var ( // NewMemberCommand returns the cobra command for "member". func NewMemberCommand() *cobra.Command { mc := &cobra.Command{ - Use: "member ", - Short: "Membership related commands", + Use: "member ", + Short: "Membership related commands. Use `etcdctl member --help` to see subcommands", + Long: "Membership related commands", + GroupID: groupClusterMaintenanceID, } mc.AddCommand(NewMemberAddCommand()) diff --git a/etcdctl/ctlv3/command/move_leader_command.go b/etcdctl/ctlv3/command/move_leader_command.go index a7b4f397b..320d9ff39 100644 --- a/etcdctl/ctlv3/command/move_leader_command.go +++ b/etcdctl/ctlv3/command/move_leader_command.go @@ -27,9 +27,10 @@ import ( // NewMoveLeaderCommand returns the cobra command for "move-leader". func NewMoveLeaderCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "move-leader ", - Short: "Transfers leadership to another etcd cluster member.", - Run: transferLeadershipCommandFunc, + Use: "move-leader ", + Short: "Transfers leadership to another etcd cluster member.", + Run: transferLeadershipCommandFunc, + GroupID: groupClusterMaintenanceID, } return cmd } diff --git a/etcdctl/ctlv3/command/put_command.go b/etcdctl/ctlv3/command/put_command.go index 4814db559..084815013 100644 --- a/etcdctl/ctlv3/command/put_command.go +++ b/etcdctl/ctlv3/command/put_command.go @@ -56,7 +56,8 @@ For example, $ cat file | put will store the content of the file to . `, - Run: putCommandFunc, + Run: putCommandFunc, + GroupID: groupKVID, } cmd.Flags().StringVar(&leaseStr, "lease", "0", "lease ID (in hexadecimal) to attach to the key") cmd.Flags().BoolVar(&putPrevKV, "prev-kv", false, "return the previous key-value pair before modification") diff --git a/etcdctl/ctlv3/command/role_command.go b/etcdctl/ctlv3/command/role_command.go index 705d88d88..5fd96ca1c 100644 --- a/etcdctl/ctlv3/command/role_command.go +++ b/etcdctl/ctlv3/command/role_command.go @@ -32,8 +32,10 @@ var ( // NewRoleCommand returns the cobra command for "role". func NewRoleCommand() *cobra.Command { ac := &cobra.Command{ - Use: "role ", - Short: "Role related commands", + Use: "role ", + Short: "Role related commands. Use `etcdctl role --help` to see subcommands", + Long: "Role related commands", + GroupID: groupAuthenticationID, } ac.AddCommand(newRoleAddCommand()) diff --git a/etcdctl/ctlv3/command/snapshot_command.go b/etcdctl/ctlv3/command/snapshot_command.go index 66934bede..d98c93b0b 100644 --- a/etcdctl/ctlv3/command/snapshot_command.go +++ b/etcdctl/ctlv3/command/snapshot_command.go @@ -52,6 +52,7 @@ func NewSnapshotCommand() *cobra.Command { Use: "snapshot ", Short: "Manages etcd node snapshots", Example: snapshotExample, + GroupID: groupClusterMaintenanceID, } cmd.AddCommand(NewSnapshotSaveCommand()) return cmd diff --git a/etcdctl/ctlv3/command/txn_command.go b/etcdctl/ctlv3/command/txn_command.go index 5433f82bd..df746a228 100644 --- a/etcdctl/ctlv3/command/txn_command.go +++ b/etcdctl/ctlv3/command/txn_command.go @@ -59,7 +59,8 @@ put key2 "some extra key" --- Refer to https://github.com/etcd-io/etcd/blob/main/etcdctl/README.md#txn-options.`, - Run: txnCommandFunc, + Run: txnCommandFunc, + GroupID: groupKVID, } cmd.Flags().BoolVarP(&txnInteractive, "interactive", "i", false, "Input transaction in interactive mode") return cmd @@ -150,16 +151,19 @@ func parseRequestUnion(line string) (*clientv3.Op, error) { opc := make(chan clientv3.Op, 1) put := NewPutCommand() + put.GroupID = "" put.Run = func(cmd *cobra.Command, args []string) { key, value, opts := getPutOp(args) opc <- clientv3.OpPut(key, value, opts...) } get := NewGetCommand() + get.GroupID = "" get.Run = func(cmd *cobra.Command, args []string) { key, opts := getGetOp(args) opc <- clientv3.OpGet(key, opts...) } del := NewDelCommand() + del.GroupID = "" del.Run = func(cmd *cobra.Command, args []string) { key, opts := getDelOp(args) opc <- clientv3.OpDelete(key, opts...) diff --git a/etcdctl/ctlv3/command/user_command.go b/etcdctl/ctlv3/command/user_command.go index f3e59a704..20e4d3e8c 100644 --- a/etcdctl/ctlv3/command/user_command.go +++ b/etcdctl/ctlv3/command/user_command.go @@ -31,8 +31,10 @@ var userShowDetail bool // NewUserCommand returns the cobra command for "user". func NewUserCommand() *cobra.Command { ac := &cobra.Command{ - Use: "user ", - Short: "User related commands", + Use: "user ", + Short: "User related commands. Use `etcdctl user --help` to see subcommands", + Long: "User related commands", + GroupID: groupAuthenticationID, } ac.AddCommand(newUserAddCommand()) diff --git a/etcdctl/ctlv3/command/version_command.go b/etcdctl/ctlv3/command/version_command.go index e57567d6a..d5be27adb 100644 --- a/etcdctl/ctlv3/command/version_command.go +++ b/etcdctl/ctlv3/command/version_command.go @@ -25,9 +25,10 @@ import ( // NewVersionCommand prints out the version of etcd. func NewVersionCommand() *cobra.Command { return &cobra.Command{ - Use: "version", - Short: "Prints the version of etcdctl", - Run: versionCommandFunc, + Use: "version", + Short: "Prints the version of etcdctl", + Run: versionCommandFunc, + GroupID: groupUtilityID, } } diff --git a/etcdctl/ctlv3/command/watch_command.go b/etcdctl/ctlv3/command/watch_command.go index fa8fc72d8..b8c8bdf13 100644 --- a/etcdctl/ctlv3/command/watch_command.go +++ b/etcdctl/ctlv3/command/watch_command.go @@ -47,9 +47,10 @@ var ( // NewWatchCommand returns the cobra command for "watch". func NewWatchCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "watch [options] [key or prefix] [range_end] [--] [exec-command arg1 arg2 ...]", - Short: "Watches events stream on keys or prefixes", - Run: watchCommandFunc, + Use: "watch [options] [key or prefix] [range_end] [--] [exec-command arg1 arg2 ...]", + Short: "Watches events stream on keys or prefixes", + Run: watchCommandFunc, + GroupID: groupKVID, } cmd.Flags().BoolVarP(&watchInteractive, "interactive", "i", false, "Interactive mode") diff --git a/etcdctl/ctlv3/ctl.go b/etcdctl/ctlv3/ctl.go index e992fa4fa..0fc657071 100644 --- a/etcdctl/ctlv3/ctl.go +++ b/etcdctl/ctlv3/ctl.go @@ -21,9 +21,7 @@ import ( "github.com/spf13/cobra" - "go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/etcdctl/v3/ctlv3/command" - "go.etcd.io/etcd/etcdctl/v3/util" "go.etcd.io/etcd/pkg/v3/cobrautl" ) @@ -76,6 +74,14 @@ func init() { rootCmd.PersistentFlags().StringVarP(&globalFlags.TLS.ServerName, "discovery-srv", "d", "", "domain name to query for SRV records describing cluster endpoints") rootCmd.PersistentFlags().StringVarP(&globalFlags.DNSClusterServiceName, "discovery-srv-name", "", "", "service name to query when using DNS discovery") + rootCmd.AddGroup( + command.NewKVGroup(), + command.NewClusterMaintenanceGroup(), + command.NewConcurrencyGroup(), + command.NewAuthenticationGroup(), + command.NewUtilityGroup(), + ) + rootCmd.AddCommand( command.NewGetCommand(), command.NewPutCommand(), @@ -101,16 +107,11 @@ func init() { command.NewCompletionCommand(), command.NewDowngradeCommand(), ) -} -func usageFunc(c *cobra.Command) error { - return util.UsageFunc(c, version.Version, version.APIVersion) + command.SetHelpCmdGroup(rootCmd) } func Start() error { - rootCmd.SetUsageFunc(usageFunc) - // Make help just show the usage - rootCmd.SetHelpTemplate(`{{.UsageString}}`) return rootCmd.Execute() } diff --git a/etcdctl/util/help.go b/etcdctl/util/help.go deleted file mode 100644 index 0e8035313..000000000 --- a/etcdctl/util/help.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2025 The etcd Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// copied from https://github.com/rkt/rkt/blob/master/rkt/help.go - -package util - -import ( - "fmt" - "io" - "os" - "strings" - "text/tabwriter" - "text/template" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -var ( - commandUsageTemplate *template.Template - templFuncs = template.FuncMap{ - "descToLines": func(s string) []string { - // trim leading/trailing whitespace and split into slice of lines - return strings.Split(strings.Trim(s, "\n\t "), "\n") - }, - "cmdName": func(cmd *cobra.Command, startCmd *cobra.Command) string { - parts := []string{cmd.Name()} - for cmd.HasParent() && cmd.Parent().Name() != startCmd.Name() { - cmd = cmd.Parent() - parts = append([]string{cmd.Name()}, parts...) - } - return strings.Join(parts, " ") - }, - "indent": func(s string) string { - pad := strings.Repeat(" ", 2) - return pad + strings.Replace(s, "\n", "\n"+pad, -1) - }, - } -) - -func init() { - commandUsage := ` -{{ $cmd := .Cmd }}\ -{{ $cmdname := cmdName .Cmd .Cmd.Root }}\ -NAME: -{{if not .Cmd.HasParent}}\ -{{printf "%s - %s" .Cmd.Name .Cmd.Short | indent}} -{{else}}\ -{{printf "%s - %s" $cmdname .Cmd.Short | indent}} -{{end}}\ - -USAGE: -{{printf "%s" .Cmd.UseLine | indent}} -{{ if not .Cmd.HasParent }}\ - -VERSION: -{{printf "%s" .Version | indent}} -{{end}}\ -{{if .Cmd.HasSubCommands}}\ - -API VERSION: -{{.APIVersion | indent}} -{{end}}\ -{{if .Cmd.HasExample}}\ - -Examples: -{{.Cmd.Example}} -{{end}}\ -{{if .Cmd.HasSubCommands}}\ - -COMMANDS: -{{range .SubCommands}}\ -{{ $cmdname := cmdName . $cmd }}\ -{{ if .Runnable }}\ -{{printf "%s\t%s" $cmdname .Short | indent}} -{{end}}\ -{{end}}\ -{{end}}\ -{{ if .Cmd.Long }}\ - -DESCRIPTION: -{{range $line := descToLines .Cmd.Long}}{{printf "%s" $line | indent}} -{{end}}\ -{{end}}\ -{{if .Cmd.HasLocalFlags}}\ - -OPTIONS: -{{.LocalFlags}}\ -{{end}}\ -{{if .Cmd.HasInheritedFlags}}\ - -GLOBAL OPTIONS: -{{.GlobalFlags}}\ -{{end}} -`[1:] - - commandUsageTemplate = template.Must(template.New("command_usage").Funcs(templFuncs).Parse(strings.ReplaceAll(commandUsage, "\\\n", ""))) -} - -func etcdFlagUsages(flagSet *pflag.FlagSet) string { - x := new(strings.Builder) - - flagSet.VisitAll(func(flag *pflag.Flag) { - if len(flag.Deprecated) > 0 { - return - } - var format string - if len(flag.Shorthand) > 0 { - format = " -%s, --%s" - } else { - format = " %s --%s" - } - if len(flag.NoOptDefVal) > 0 { - format = format + "[" - } - if flag.Value.Type() == "string" { - // put quotes on the value - format = format + "=%q" - } else { - format = format + "=%s" - } - if len(flag.NoOptDefVal) > 0 { - format = format + "]" - } - format = format + "\t%s\n" - shorthand := flag.Shorthand - fmt.Fprintf(x, format, shorthand, flag.Name, flag.DefValue, flag.Usage) - }) - - return x.String() -} - -func getSubCommands(cmd *cobra.Command) []*cobra.Command { - var subCommands []*cobra.Command - for _, subCmd := range cmd.Commands() { - subCommands = append(subCommands, subCmd) - subCommands = append(subCommands, getSubCommands(subCmd)...) - } - return subCommands -} - -func UsageFunc(cmd *cobra.Command, version, APIVersion string) error { - subCommands := getSubCommands(cmd) - tabOut := getTabOutWithWriter(os.Stdout) - commandUsageTemplate.Execute(tabOut, struct { - Cmd *cobra.Command - LocalFlags string - GlobalFlags string - SubCommands []*cobra.Command - Version string - APIVersion string - }{ - cmd, - etcdFlagUsages(cmd.LocalFlags()), - etcdFlagUsages(cmd.InheritedFlags()), - subCommands, - version, - APIVersion, - }) - tabOut.Flush() - return nil -} - -func getTabOutWithWriter(writer io.Writer) *tabwriter.Writer { - aTabOut := new(tabwriter.Writer) - aTabOut.Init(writer, 0, 8, 1, '\t', 0) - return aTabOut -}