From b411fe217f5c78d43e25f714d3e4ef86052ae199 Mon Sep 17 00:00:00 2001 From: AdoHe Date: Sat, 20 Aug 2016 08:03:39 +0800 Subject: [PATCH] update kubectl help output for better organization --- .generated_docs | 3 + docs/man/man1/kubectl-options.1 | 3 + docs/user-guide/kubectl/kubectl_options.md | 36 +++ docs/yaml/kubectl/kubectl_options.yaml | 3 + pkg/kubectl/cmd/cmd.go | 136 ++++++---- pkg/kubectl/cmd/options.go | 39 +++ pkg/kubectl/cmd/templates/templater.go | 300 +++++++++++++++++++++ pkg/kubectl/cmd/templates/templates.go | 99 +++++++ 8 files changed, 573 insertions(+), 46 deletions(-) create mode 100644 docs/man/man1/kubectl-options.1 create mode 100644 docs/user-guide/kubectl/kubectl_options.md create mode 100644 docs/yaml/kubectl/kubectl_options.yaml create mode 100644 pkg/kubectl/cmd/options.go create mode 100644 pkg/kubectl/cmd/templates/templater.go create mode 100644 pkg/kubectl/cmd/templates/templates.go diff --git a/.generated_docs b/.generated_docs index c2fae3285d4..e88bda62ae5 100644 --- a/.generated_docs +++ b/.generated_docs @@ -51,6 +51,7 @@ docs/man/man1/kubectl-get.1 docs/man/man1/kubectl-label.1 docs/man/man1/kubectl-logs.1 docs/man/man1/kubectl-namespace.1 +docs/man/man1/kubectl-options.1 docs/man/man1/kubectl-patch.1 docs/man/man1/kubectl-port-forward.1 docs/man/man1/kubectl-proxy.1 @@ -120,6 +121,7 @@ docs/user-guide/kubectl/kubectl_get.md docs/user-guide/kubectl/kubectl_label.md docs/user-guide/kubectl/kubectl_logs.md docs/user-guide/kubectl/kubectl_namespace.md +docs/user-guide/kubectl/kubectl_options.md docs/user-guide/kubectl/kubectl_patch.md docs/user-guide/kubectl/kubectl_port-forward.md docs/user-guide/kubectl/kubectl_proxy.md @@ -164,6 +166,7 @@ docs/yaml/kubectl/kubectl_get.yaml docs/yaml/kubectl/kubectl_label.yaml docs/yaml/kubectl/kubectl_logs.yaml docs/yaml/kubectl/kubectl_namespace.yaml +docs/yaml/kubectl/kubectl_options.yaml docs/yaml/kubectl/kubectl_patch.yaml docs/yaml/kubectl/kubectl_port-forward.yaml docs/yaml/kubectl/kubectl_proxy.yaml diff --git a/docs/man/man1/kubectl-options.1 b/docs/man/man1/kubectl-options.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-options.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/user-guide/kubectl/kubectl_options.md b/docs/user-guide/kubectl/kubectl_options.md new file mode 100644 index 00000000000..8f90c5c1cb1 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_options.md @@ -0,0 +1,36 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

PLEASE NOTE: This document applies to the HEAD of the source tree

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_options.md?pixel)]() + diff --git a/docs/yaml/kubectl/kubectl_options.yaml b/docs/yaml/kubectl/kubectl_options.yaml new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/yaml/kubectl/kubectl_options.yaml @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 958cc533351..273e59a16e3 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -17,16 +17,18 @@ limitations under the License. package cmd import ( + "fmt" "io" - "github.com/golang/glog" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config" "k8s.io/kubernetes/pkg/kubectl/cmd/rollout" "k8s.io/kubernetes/pkg/kubectl/cmd/set" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/util/flag" + "github.com/golang/glog" "github.com/spf13/cobra" ) @@ -222,59 +224,89 @@ Find more information at https://github.com/kubernetes/kubernetes.`, Run: runHelp, BashCompletionFunction: bash_completion_func, } - cmds.SetHelpTemplate(help_template) - cmds.SetUsageTemplate(usage_template) f.BindFlags(cmds.PersistentFlags()) f.BindExternalFlags(cmds.PersistentFlags()) - cmds.SetHelpCommand(NewCmdHelp(f, out)) - // From this point and forward we get warnings on flags that contain "_" separators cmds.SetGlobalNormalizationFunc(flag.WarnWordSepNormalizeFunc) - cmds.AddCommand(NewCmdGet(f, out, err)) - cmds.AddCommand(set.NewCmdSet(f, out)) - cmds.AddCommand(NewCmdDescribe(f, out)) - cmds.AddCommand(NewCmdCreate(f, out)) - cmds.AddCommand(NewCmdReplace(f, out)) - cmds.AddCommand(NewCmdPatch(f, out)) - cmds.AddCommand(NewCmdDelete(f, out)) - cmds.AddCommand(NewCmdEdit(f, out, err)) - cmds.AddCommand(NewCmdApply(f, out)) + groups := templates.CommandGroups{ + { + Message: "Basic Commands (Beginner):", + Commands: []*cobra.Command{ + NewCmdCreate(f, out), + NewCmdExposeService(f, out), + NewCmdRun(f, in, out, err), + set.NewCmdSet(f, out), + }, + }, + { + Message: "Basic Commands (Intermediate):", + Commands: []*cobra.Command{ + NewCmdGet(f, out, err), + NewCmdExplain(f, out), + NewCmdEdit(f, out, err), + NewCmdDelete(f, out), + }, + }, + { + Message: "Deploy Commands:", + Commands: []*cobra.Command{ + rollout.NewCmdRollout(f, out), + NewCmdRollingUpdate(f, out), + NewCmdScale(f, out), + NewCmdAutoscale(f, out), + }, + }, + { + Message: "Cluster Management Commands:", + Commands: []*cobra.Command{ + NewCmdClusterInfo(f, out), + NewCmdTop(f, out), + NewCmdCordon(f, out), + NewCmdUncordon(f, out), + NewCmdDrain(f, out), + NewCmdTaint(f, out), + }, + }, + { + Message: "Troubleshooting and Debugging Commands:", + Commands: []*cobra.Command{ + NewCmdDescribe(f, out), + NewCmdLogs(f, out), + NewCmdAttach(f, in, out, err), + NewCmdExec(f, in, out, err), + NewCmdPortForward(f, out, err), + NewCmdProxy(f, out), + }, + }, + { + Message: "Advanced Commands:", + Commands: []*cobra.Command{ + NewCmdApply(f, out), + NewCmdPatch(f, out), + NewCmdReplace(f, out), + NewCmdConvert(f, out), + }, + }, + { + Message: "Settings Commands:", + Commands: []*cobra.Command{ + NewCmdLabel(f, out), + NewCmdAnnotate(f, out), + NewCmdCompletion(f, out), + }, + }, + } + groups.Add(cmds) - cmds.AddCommand(NewCmdNamespace(out)) - cmds.AddCommand(NewCmdLogs(f, out)) - cmds.AddCommand(NewCmdRollingUpdate(f, out)) - cmds.AddCommand(NewCmdScale(f, out)) - cmds.AddCommand(NewCmdCordon(f, out)) - cmds.AddCommand(NewCmdDrain(f, out)) - cmds.AddCommand(NewCmdUncordon(f, out)) - - cmds.AddCommand(NewCmdAttach(f, in, out, err)) - cmds.AddCommand(NewCmdExec(f, in, out, err)) - cmds.AddCommand(NewCmdPortForward(f, out, err)) - cmds.AddCommand(NewCmdProxy(f, out)) - - cmds.AddCommand(NewCmdRun(f, in, out, err)) - cmds.AddCommand(NewCmdStop(f, out)) - cmds.AddCommand(NewCmdExposeService(f, out)) - cmds.AddCommand(NewCmdAutoscale(f, out)) - cmds.AddCommand(rollout.NewCmdRollout(f, out)) - - cmds.AddCommand(NewCmdLabel(f, out)) - cmds.AddCommand(NewCmdAnnotate(f, out)) - cmds.AddCommand(NewCmdTaint(f, out)) - - cmds.AddCommand(cmdconfig.NewCmdConfig(clientcmd.NewDefaultPathOptions(), out)) - cmds.AddCommand(NewCmdClusterInfo(f, out)) - cmds.AddCommand(NewCmdApiVersions(f, out)) - cmds.AddCommand(NewCmdVersion(f, out)) - cmds.AddCommand(NewCmdExplain(f, out)) - cmds.AddCommand(NewCmdConvert(f, out)) - cmds.AddCommand(NewCmdCompletion(f, out)) - - cmds.AddCommand(NewCmdTop(f, out)) + filters := []string{ + "options", + Deprecated("kubectl", "delete", cmds, NewCmdStop(f, out)), + Deprecated("kubectl", "config set-context", cmds, NewCmdNamespace(out)), + } + templates.ActsAsRootCommand(cmds, filters, groups...) if cmds.Flag("namespace") != nil { if cmds.Flag("namespace").Annotations == nil { @@ -286,6 +318,11 @@ Find more information at https://github.com/kubernetes/kubernetes.`, ) } + cmds.AddCommand(cmdconfig.NewCmdConfig(clientcmd.NewDefaultPathOptions(), out)) + cmds.AddCommand(NewCmdVersion(f, out)) + cmds.AddCommand(NewCmdApiVersions(f, out)) + cmds.AddCommand(NewCmdOptions(out)) + return cmds } @@ -296,3 +333,10 @@ func runHelp(cmd *cobra.Command, args []string) { func printDeprecationWarning(command, alias string) { glog.Warningf("%s is DEPRECATED and will be removed in a future version. Use %s instead.", alias, command) } + +func Deprecated(baseName, to string, parent, cmd *cobra.Command) string { + cmd.Long = fmt.Sprintf("Deprecated: This command is deprecated, all its functionalities are covered by \"%s %s\"", baseName, to) + cmd.Short = fmt.Sprintf("Deprecated: %s", to) + parent.AddCommand(cmd) + return cmd.Name() +} diff --git a/pkg/kubectl/cmd/options.go b/pkg/kubectl/cmd/options.go new file mode 100644 index 00000000000..a3538ef9fb4 --- /dev/null +++ b/pkg/kubectl/cmd/options.go @@ -0,0 +1,39 @@ +/* +Copyright 2016 The Kubernetes 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 cmd + +import ( + "io" + + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" + + "github.com/spf13/cobra" +) + +// NewCmdOptions implements the options command +func NewCmdOptions(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "options", + Run: func(cmd *cobra.Command, args []string) { + cmd.Usage() + }, + } + + templates.UseOptionsTemplates(cmd) + + return cmd +} diff --git a/pkg/kubectl/cmd/templates/templater.go b/pkg/kubectl/cmd/templates/templater.go new file mode 100644 index 00000000000..b51f45a1630 --- /dev/null +++ b/pkg/kubectl/cmd/templates/templater.go @@ -0,0 +1,300 @@ +/* +Copyright 2016 The Kubernetes 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 templates + +import ( + "bytes" + "fmt" + "strings" + "text/template" + "unicode" + + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" +) + +// Content of this package was borrowed from openshift/origin. + +type CommandGroup struct { + Message string + Commands []*cobra.Command +} + +type CommandGroups []CommandGroup + +func (g CommandGroups) Add(c *cobra.Command) { + for _, group := range g { + for _, command := range group.Commands { + c.AddCommand(command) + } + } +} + +func (g CommandGroups) Has(c *cobra.Command) bool { + for _, group := range g { + for _, command := range group.Commands { + if command == c { + return true + } + } + } + return false +} + +func AddAdditionalCommands(g CommandGroups, message string, cmds []*cobra.Command) CommandGroups { + group := CommandGroup{Message: message} + for _, c := range cmds { + // Don't show commands that has no short description + if !g.Has(c) && len(c.Short) != 0 { + group.Commands = append(group.Commands, c) + } + } + if len(group.Commands) == 0 { + return g + } + return append(g, group) +} + +func filter(cmds []*cobra.Command, names ...string) []*cobra.Command { + out := []*cobra.Command{} + for _, c := range cmds { + if c.Hidden { + continue + } + skip := false + for _, name := range names { + if name == c.Name() { + skip = true + break + } + } + if skip { + continue + } + out = append(out, c) + } + return out +} + +type FlagExposer interface { + ExposeFlags(cmd *cobra.Command, flags ...string) FlagExposer +} + +func ActsAsRootCommand(cmd *cobra.Command, filters []string, groups ...CommandGroup) FlagExposer { + if cmd == nil { + panic("nil root command") + } + cmd.SetHelpTemplate(MainHelpTemplate()) + templater := &templater{ + RootCmd: cmd, + UsageTemplate: MainUsageTemplate(), + CommandGroups: groups, + Filtered: filters, + } + cmd.SetUsageFunc(templater.UsageFunc()) + return templater +} + +func UseOptionsTemplates(cmd *cobra.Command) { + cmd.SetHelpTemplate(OptionsHelpTemplate()) + templater := &templater{ + UsageTemplate: OptionsUsageTemplate(), + } + cmd.SetUsageFunc(templater.UsageFunc()) +} + +type templater struct { + UsageTemplate string + RootCmd *cobra.Command + CommandGroups + Filtered []string +} + +func (templater *templater) ExposeFlags(cmd *cobra.Command, flags ...string) FlagExposer { + cmd.SetUsageFunc(templater.UsageFunc(flags...)) + return templater +} + +func (templater *templater) UsageFunc(exposedFlags ...string) func(*cobra.Command) error { + return func(c *cobra.Command) error { + t := template.New("custom") + + t.Funcs(template.FuncMap{ + "trim": strings.TrimSpace, + "trimRight": func(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) }, + "trimLeft": func(s string) string { return strings.TrimLeftFunc(s, unicode.IsSpace) }, + "gt": cobra.Gt, + "eq": cobra.Eq, + "rpad": rpad, + "appendIfNotPresent": appendIfNotPresent, + "flagsNotIntersected": flagsNotIntersected, + "visibleFlags": visibleFlags, + "flagsUsages": flagsUsages, + "indentLines": indentLines, + "cmdGroups": templater.cmdGroups, + "rootCmd": templater.rootCmdName, + "isRootCmd": templater.isRootCmd, + "optionsCmdFor": templater.optionsCmdFor, + "usageLine": templater.usageLine, + "exposed": func(c *cobra.Command) *flag.FlagSet { + exposed := flag.NewFlagSet("exposed", flag.ContinueOnError) + if len(exposedFlags) > 0 { + for _, name := range exposedFlags { + if flag := c.Flags().Lookup(name); flag != nil { + exposed.AddFlag(flag) + } + } + } + return exposed + }, + }) + + template.Must(t.Parse(templater.UsageTemplate)) + return t.Execute(c.OutOrStdout(), c) + } +} + +func (templater *templater) cmdGroups(c *cobra.Command, all []*cobra.Command) []CommandGroup { + if len(templater.CommandGroups) > 0 && c == templater.RootCmd { + all = filter(all, templater.Filtered...) + return AddAdditionalCommands(templater.CommandGroups, "Other Commands:", all) + } + all = filter(all, "options") + return []CommandGroup{ + { + Message: "Available Commands:", + Commands: all, + }, + } +} + +func (t *templater) rootCmdName(c *cobra.Command) string { + return t.rootCmd(c).CommandPath() +} + +func (t *templater) isRootCmd(c *cobra.Command) bool { + return t.rootCmd(c) == c +} + +func (t *templater) parents(c *cobra.Command) []*cobra.Command { + parents := []*cobra.Command{c} + for current := c; !t.isRootCmd(current) && current.HasParent(); { + current = current.Parent() + parents = append(parents, current) + } + return parents +} + +func (t *templater) rootCmd(c *cobra.Command) *cobra.Command { + if c != nil && !c.HasParent() { + return c + } + if t.RootCmd == nil { + panic("nil root cmd") + } + return t.RootCmd +} + +func (t *templater) optionsCmdFor(c *cobra.Command) string { + if !c.Runnable() { + return "" + } + rootCmdStructure := t.parents(c) + for i := len(rootCmdStructure) - 1; i >= 0; i-- { + cmd := rootCmdStructure[i] + if _, _, err := cmd.Find([]string{"options"}); err == nil { + return cmd.CommandPath() + " options" + } + } + return "" +} + +func (t *templater) usageLine(c *cobra.Command) string { + usage := c.UseLine() + suffix := "[options]" + if c.HasFlags() && !strings.Contains(usage, suffix) { + usage += " " + suffix + } + return usage +} + +func flagsUsages(f *flag.FlagSet) string { + x := new(bytes.Buffer) + + f.VisitAll(func(flag *flag.Flag) { + if flag.Hidden { + return + } + format := "--%s=%s: %s\n" + + if flag.Value.Type() == "string" { + format = "--%s='%s': %s\n" + } + + if len(flag.Shorthand) > 0 { + format = " -%s, " + format + } else { + format = " %s " + format + } + + fmt.Fprintf(x, format, flag.Shorthand, flag.Name, flag.DefValue, flag.Usage) + }) + + return x.String() +} + +func rpad(s string, padding int) string { + template := fmt.Sprintf("%%-%ds", padding) + return fmt.Sprintf(template, s) +} + +func indentLines(s string, indentation int) string { + r := []string{} + for _, line := range strings.Split(s, "\n") { + indented := strings.Repeat(" ", indentation) + line + r = append(r, indented) + } + return strings.Join(r, "\n") +} + +func appendIfNotPresent(s, stringToAppend string) string { + if strings.Contains(s, stringToAppend) { + return s + } + return s + " " + stringToAppend +} + +func flagsNotIntersected(l *flag.FlagSet, r *flag.FlagSet) *flag.FlagSet { + f := flag.NewFlagSet("notIntersected", flag.ContinueOnError) + l.VisitAll(func(flag *flag.Flag) { + if r.Lookup(flag.Name) == nil { + f.AddFlag(flag) + } + }) + return f +} + +func visibleFlags(l *flag.FlagSet) *flag.FlagSet { + hidden := "help" + f := flag.NewFlagSet("visible", flag.ContinueOnError) + l.VisitAll(func(flag *flag.Flag) { + if flag.Name != hidden { + f.AddFlag(flag) + } + }) + return f +} diff --git a/pkg/kubectl/cmd/templates/templates.go b/pkg/kubectl/cmd/templates/templates.go new file mode 100644 index 00000000000..d89e6007481 --- /dev/null +++ b/pkg/kubectl/cmd/templates/templates.go @@ -0,0 +1,99 @@ +/* +Copyright 2016 The Kubernetes 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 templates + +import "strings" + +func MainHelpTemplate() string { + return decorate(mainHelpTemplate, false) +} + +func MainUsageTemplate() string { + return decorate(mainUsageTemplate, true) + "\n" +} + +func OptionsHelpTemplate() string { + return decorate(optionsHelpTemplate, false) +} + +func OptionsUsageTemplate() string { + return decorate(optionsUsageTemplate, false) +} + +func decorate(template string, trim bool) string { + if trim && len(strings.Trim(template, " ")) > 0 { + template = strings.Trim(template, "\n") + } + return template +} + +const ( + vars = `{{$isRootCmd := isRootCmd .}}` + + `{{$rootCmd := rootCmd .}}` + + `{{$visibleFlags := visibleFlags (flagsNotIntersected .LocalFlags .PersistentFlags)}}` + + `{{$explicitlyExposedFlags := exposed .}}` + + `{{$optionsCmdFor := optionsCmdFor .}}` + + `{{$usageLine := usageLine .}}` + + mainHelpTemplate = `{{with or .Long .Short }}{{. | trim}}{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` + + mainUsageTemplate = vars + + // ALIASES + `{{if gt .Aliases 0}} + +Aliases: +{{.NameAndAliases}}{{end}}` + + + // EXAMPLES + `{{if .HasExample}} + +Examples: +{{ indentLines (.Example | trimLeft) 2 }}{{end}}` + + + // SUBCOMMANDS + `{{ if .HasAvailableSubCommands}} +{{range cmdGroups . .Commands}} +{{.Message}} +{{range .Commands}}{{if .Runnable}} {{rpad .Name .NamePadding }} {{.Short}} +{{end}}{{end}}{{end}}{{end}}` + + + // VISIBLE FLAGS + `{{ if or $visibleFlags.HasFlags $explicitlyExposedFlags.HasFlags}} + +Options: +{{ if $visibleFlags.HasFlags}}{{flagsUsages $visibleFlags}}{{end}}{{ if $explicitlyExposedFlags.HasFlags}}{{flagsUsages $explicitlyExposedFlags}}{{end}}{{end}}` + + + // USAGE LINE + `{{if and .Runnable (ne .UseLine "") (ne .UseLine $rootCmd)}} +Usage: + {{$usageLine}} +{{end}}` + + + // TIPS: --help + `{{ if .HasSubCommands }} +Use "{{$rootCmd}} --help" for more information about a given command.{{end}}` + + + // TIPS: global options + `{{ if $optionsCmdFor}} +Use "{{$optionsCmdFor}}" for a list of global command-line options (applies to all commands).{{end}}` + + optionsHelpTemplate = `` + + optionsUsageTemplate = `{{ if .HasInheritedFlags}}The following options can be passed to any command: + +{{flagsUsages .InheritedFlags}}{{end}}` +)