diff --git a/etcdctl/ctlv3/command/snapshot_command.go b/etcdctl/ctlv3/command/snapshot_command.go index df317e23cc7..983cf93ad16 100644 --- a/etcdctl/ctlv3/command/snapshot_command.go +++ b/etcdctl/ctlv3/command/snapshot_command.go @@ -23,14 +23,37 @@ import ( "go.etcd.io/etcd/client/pkg/v3/logutil" snapshot "go.etcd.io/etcd/client/v3/snapshot" + "go.etcd.io/etcd/etcdctl/v3/util" "go.etcd.io/etcd/pkg/v3/cobrautl" ) +var ( + snapshotExample = util.Normalize(` + # Save snapshot to a given file + etcdctl snapshot save /backup/etcd-snapshot.db + + # Get snapshot from given address and save it to file + etcdctl snapshot save --endpoints=127.0.0.1:3000 /backup/etcd-snapshot.db + + # Get snapshot from given address with certificates + etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/etcd/ca.crt --cert=/etc/etcd/etcd.crt --key=/etc/etcd/etcd.key snapshot save /backup/etcd-snapshot.db + + # Get snapshot wih certain user and password + etcdctl --user=root --password=password123 snapshot save /backup/etcd-snapshot.db + + # Get snapshot from given address with timeout + etcdctl --endpoints=https://127.0.0.1:2379 --dial-timeout=20s snapshot save /backup/etcd-snapshot.db + + # Save snapshot with desirable time format + etcdctl snapshot save /mnt/backup/etcd/backup_$(date +%Y%m%d_%H%M%S).db`) +) + // NewSnapshotCommand returns the cobra command for "snapshot". func NewSnapshotCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "snapshot ", - Short: "Manages etcd node snapshots", + Use: "snapshot ", + Short: "Manages etcd node snapshots", + Example: snapshotExample, } cmd.AddCommand(NewSnapshotSaveCommand()) return cmd @@ -38,15 +61,16 @@ func NewSnapshotCommand() *cobra.Command { func NewSnapshotSaveCommand() *cobra.Command { return &cobra.Command{ - Use: "save ", - Short: "Stores an etcd node backend snapshot to a given file", - Run: snapshotSaveCommandFunc, + Use: "save ", + Short: "Stores an etcd node backend snapshot to a given file", + Run: snapshotSaveCommandFunc, + Example: snapshotExample, } } func snapshotSaveCommandFunc(cmd *cobra.Command, args []string) { if len(args) != 1 { - err := fmt.Errorf("snapshot save expects one argument") + err := fmt.Errorf("snapshot save expects one argument ") cobrautl.ExitWithError(cobrautl.ExitBadArgs, err) } diff --git a/etcdctl/util/normalizer.go b/etcdctl/util/normalizer.go new file mode 100644 index 00000000000..99e11e077ce --- /dev/null +++ b/etcdctl/util/normalizer.go @@ -0,0 +1,49 @@ +// Copyright 2024 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 util + +import "strings" + +const indentation = " " + +// Normalize normalizes a string: +// 1. trim the leading and trailing space +// 2. add an indentation before each line +func Normalize(s string) string { + if len(s) == 0 { + return s + } + return normalizer{s}.trim().indent().string +} + +type normalizer struct { + string +} + +func (n normalizer) trim() normalizer { + n.string = strings.TrimSpace(n.string) + return n +} + +func (n normalizer) indent() normalizer { + indentedLines := []string{} + for _, line := range strings.Split(n.string, "\n") { + trimmed := strings.TrimSpace(line) + indented := indentation + trimmed + indentedLines = append(indentedLines, indented) + } + n.string = strings.Join(indentedLines, "\n") + return n +} diff --git a/pkg/cobrautl/help.go b/pkg/cobrautl/help.go index d521bf9f0ad..574578199e7 100644 --- a/pkg/cobrautl/help.go +++ b/pkg/cobrautl/help.go @@ -43,6 +43,10 @@ var ( } return strings.Join(parts, " ") }, + "indent": func(s string) string { + pad := strings.Repeat(" ", 2) + return pad + strings.Replace(s, "\n", "\n"+pad, -1) + }, } ) @@ -51,39 +55,43 @@ func init() { {{ $cmd := .Cmd }}\ {{ $cmdname := cmdName .Cmd .Cmd.Root }}\ NAME: -{{ if not .Cmd.HasParent }}\ -{{printf "\t%s - %s" .Cmd.Name .Cmd.Short}} +{{if not .Cmd.HasParent}}\ +{{printf "%s - %s" .Cmd.Name .Cmd.Short | indent}} {{else}}\ -{{printf "\t%s - %s" $cmdname .Cmd.Short}} +{{printf "%s - %s" $cmdname .Cmd.Short | indent}} {{end}}\ USAGE: -{{printf "\t%s" .Cmd.UseLine}} +{{printf "%s" .Cmd.UseLine | indent}} {{ if not .Cmd.HasParent }}\ VERSION: -{{printf "\t%s" .Version}} +{{printf "%s" .Version | indent}} {{end}}\ {{if .Cmd.HasSubCommands}}\ API VERSION: -{{printf "\t%s" .APIVersion}} +{{.APIVersion | indent}} {{end}}\ -{{if .Cmd.HasSubCommands}}\ +{{if .Cmd.HasExample}}\ +Examples: +{{.Cmd.Example}} +{{end}}\ +{{if .Cmd.HasSubCommands}}\ COMMANDS: {{range .SubCommands}}\ {{ $cmdname := cmdName . $cmd }}\ {{ if .Runnable }}\ -{{printf "\t%s\t%s" $cmdname .Short}} +{{printf "%s\t%s" $cmdname .Short | indent}} {{end}}\ {{end}}\ {{end}}\ {{ if .Cmd.Long }}\ DESCRIPTION: -{{range $line := descToLines .Cmd.Long}}{{printf "\t%s" $line}} +{{range $line := descToLines .Cmd.Long}}{{printf "%s" $line | indent}} {{end}}\ {{end}}\ {{if .Cmd.HasLocalFlags}}\