Make kubectl bash completion namespace aware and add noun aliases

- add namespace filtering to bash completion
- add --namespace flag bash completion
- add bash completion noun aliases
- adapt to new cobra package structure
This commit is contained in:
Dr. Stefan Schimanski 2016-03-20 19:14:25 +01:00
parent 8af3be87e4
commit 7e0bb885f1
8 changed files with 99 additions and 17 deletions

View File

@ -21,7 +21,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra/doc"
"k8s.io/kubernetes/cmd/genutils" "k8s.io/kubernetes/cmd/genutils"
"k8s.io/kubernetes/pkg/kubectl/cmd" "k8s.io/kubernetes/pkg/kubectl/cmd"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
@ -48,5 +48,5 @@ func main() {
os.Setenv("HOME", "/home/username") os.Setenv("HOME", "/home/username")
// TODO os.Stdin should really be something like ioutil.Discard, but a Reader // TODO os.Stdin should really be something like ioutil.Discard, but a Reader
kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard)
cobra.GenMarkdownTree(kubectl, outDir) doc.GenMarkdownTree(kubectl, outDir)
} }

View File

@ -20,7 +20,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra/doc"
"k8s.io/kubernetes/cmd/genutils" "k8s.io/kubernetes/cmd/genutils"
apiservapp "k8s.io/kubernetes/cmd/kube-apiserver/app" apiservapp "k8s.io/kubernetes/cmd/kube-apiserver/app"
cmapp "k8s.io/kubernetes/cmd/kube-controller-manager/app" cmapp "k8s.io/kubernetes/cmd/kube-controller-manager/app"
@ -51,23 +51,23 @@ func main() {
case "kube-apiserver": case "kube-apiserver":
// generate docs for kube-apiserver // generate docs for kube-apiserver
apiserver := apiservapp.NewAPIServerCommand() apiserver := apiservapp.NewAPIServerCommand()
cobra.GenMarkdownTree(apiserver, outDir) doc.GenMarkdownTree(apiserver, outDir)
case "kube-controller-manager": case "kube-controller-manager":
// generate docs for kube-controller-manager // generate docs for kube-controller-manager
controllermanager := cmapp.NewControllerManagerCommand() controllermanager := cmapp.NewControllerManagerCommand()
cobra.GenMarkdownTree(controllermanager, outDir) doc.GenMarkdownTree(controllermanager, outDir)
case "kube-proxy": case "kube-proxy":
// generate docs for kube-proxy // generate docs for kube-proxy
proxy := proxyapp.NewProxyCommand() proxy := proxyapp.NewProxyCommand()
cobra.GenMarkdownTree(proxy, outDir) doc.GenMarkdownTree(proxy, outDir)
case "kube-scheduler": case "kube-scheduler":
// generate docs for kube-scheduler // generate docs for kube-scheduler
scheduler := schapp.NewSchedulerCommand() scheduler := schapp.NewSchedulerCommand()
cobra.GenMarkdownTree(scheduler, outDir) doc.GenMarkdownTree(scheduler, outDir)
case "kubelet": case "kubelet":
// generate docs for kubelet // generate docs for kubelet
kubelet := kubeletapp.NewKubeletCommand() kubelet := kubeletapp.NewKubeletCommand()
cobra.GenMarkdownTree(kubelet, outDir) doc.GenMarkdownTree(kubelet, outDir)
default: default:
fmt.Fprintf(os.Stderr, "Module %s is not supported", module) fmt.Fprintf(os.Stderr, "Module %s is not supported", module)
os.Exit(1) os.Exit(1)

View File

@ -30,12 +30,47 @@ import (
const ( const (
bash_completion_func = `# call kubectl get $1, bash_completion_func = `# call kubectl get $1,
__kubectl_namespace_flag()
{
local ret two_word_ns
ret=""
two_word_ns=false
for w in "${words[@]}"; do
if [ "$two_word_ns" = true ]; then
ret="--namespace=${w}"
two_word_ns=false
continue
fi
case "${w}" in
--namespace=*)
ret=${w}
;;
--namespace)
two_word_ns=true
;;
--all-namespaces)
ret=${w}
;;
esac
done
echo $ret
}
__kubectl_get_namespaces()
{
local template kubectl_out
template="{{ range .items }}{{ .metadata.name }} {{ end }}"
if kubectl_out=$(kubectl get -o template --template="${template}" namespace 2>/dev/null); then
COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
fi
}
__kubectl_parse_get() __kubectl_parse_get()
{ {
local template local template
template="{{ range .items }}{{ .metadata.name }} {{ end }}" template="{{ range .items }}{{ .metadata.name }} {{ end }}"
local kubectl_out local kubectl_out
if kubectl_out=$(kubectl get -o template --template="${template}" "$1" 2>/dev/null); then if kubectl_out=$(kubectl get $(__kubectl_namespace_flag) -o template --template="${template}" "$1" 2>/dev/null); then
COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) ) COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
fi fi
} }
@ -71,7 +106,7 @@ __kubectl_get_containers()
fi fi
local last=${nouns[${len} -1]} local last=${nouns[${len} -1]}
local kubectl_out local kubectl_out
if kubectl_out=$(kubectl get -o template --template="${template}" pods "${last}" 2>/dev/null); then if kubectl_out=$(kubectl get $(__kubectl_namespace_flag) -o template --template="${template}" pods "${last}" 2>/dev/null); then
COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) ) COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
fi fi
} }
@ -196,6 +231,14 @@ Find more information at https://github.com/kubernetes/kubernetes.`,
cmds.AddCommand(NewCmdExplain(f, out)) cmds.AddCommand(NewCmdExplain(f, out))
cmds.AddCommand(NewCmdConvert(f, out)) cmds.AddCommand(NewCmdConvert(f, out))
if cmds.Flag("namespace").Annotations == nil {
cmds.Flag("namespace").Annotations = map[string][]string{}
}
cmds.Flag("namespace").Annotations[cobra.BashCompCustom] = append(
cmds.Flag("namespace").Annotations[cobra.BashCompCustom],
"__kubectl_get_namespaces",
)
return cmds return cmds
} }

View File

@ -71,11 +71,12 @@ func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command {
options := &DeleteOptions{} options := &DeleteOptions{}
// retrieve a list of handled resources from printer as valid args // retrieve a list of handled resources from printer as valid args
validArgs := []string{} validArgs, argAliases := []string{}, []string{}
p, err := f.Printer(nil, false, false, false, false, false, false, []string{}) p, err := f.Printer(nil, false, false, false, false, false, false, []string{})
cmdutil.CheckErr(err) cmdutil.CheckErr(err)
if p != nil { if p != nil {
validArgs = p.HandledResources() validArgs = p.HandledResources()
argAliases = kubectl.ResourceAliases(validArgs)
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -88,7 +89,8 @@ func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command {
err := RunDelete(f, out, cmd, args, options) err := RunDelete(f, out, cmd, args, options)
cmdutil.CheckErr(err) cmdutil.CheckErr(err)
}, },
ValidArgs: validArgs, ValidArgs: validArgs,
ArgAliases: argAliases,
} }
usage := "Filename, directory, or URL to a file containing the resource to delete." usage := "Filename, directory, or URL to a file containing the resource to delete."
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)

View File

@ -74,6 +74,9 @@ kubectl describe pods frontend`
func NewCmdDescribe(f *cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdDescribe(f *cmdutil.Factory, out io.Writer) *cobra.Command {
options := &DescribeOptions{} options := &DescribeOptions{}
validArgs := kubectl.DescribableResources()
argAliases := kubectl.ResourceAliases(validArgs)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME)", Use: "describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME)",
Short: "Show details of a specific resource or group of resources", Short: "Show details of a specific resource or group of resources",
@ -83,7 +86,8 @@ func NewCmdDescribe(f *cmdutil.Factory, out io.Writer) *cobra.Command {
err := RunDescribe(f, out, cmd, args, options) err := RunDescribe(f, out, cmd, args, options)
cmdutil.CheckErr(err) cmdutil.CheckErr(err)
}, },
ValidArgs: kubectl.DescribableResources(), ValidArgs: validArgs,
ArgAliases: argAliases,
} }
usage := "Filename, directory, or URL to a file containing the resource to describe" usage := "Filename, directory, or URL to a file containing the resource to describe"
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)

View File

@ -74,11 +74,12 @@ func NewCmdGet(f *cmdutil.Factory, out io.Writer) *cobra.Command {
options := &GetOptions{} options := &GetOptions{}
// retrieve a list of handled resources from printer as valid args // retrieve a list of handled resources from printer as valid args
validArgs := []string{} validArgs, argAliases := []string{}, []string{}
p, err := f.Printer(nil, false, false, false, false, false, false, []string{}) p, err := f.Printer(nil, false, false, false, false, false, false, []string{})
cmdutil.CheckErr(err) cmdutil.CheckErr(err)
if p != nil { if p != nil {
validArgs = p.HandledResources() validArgs = p.HandledResources()
argAliases = kubectl.ResourceAliases(validArgs)
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -90,7 +91,8 @@ func NewCmdGet(f *cmdutil.Factory, out io.Writer) *cobra.Command {
err := RunGet(f, out, cmd, args, options) err := RunGet(f, out, cmd, args, options)
cmdutil.CheckErr(err) cmdutil.CheckErr(err)
}, },
ValidArgs: validArgs, ValidArgs: validArgs,
ArgAliases: argAliases,
} }
cmdutil.AddPrinterFlags(cmd) cmdutil.AddPrinterFlags(cmd)
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on") cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on")

View File

@ -73,11 +73,12 @@ func NewCmdLabel(f *cmdutil.Factory, out io.Writer) *cobra.Command {
options := &LabelOptions{} options := &LabelOptions{}
// retrieve a list of handled resources from printer as valid args // retrieve a list of handled resources from printer as valid args
validArgs := []string{} validArgs, argAliases := []string{}, []string{}
p, err := f.Printer(nil, false, false, false, false, false, false, []string{}) p, err := f.Printer(nil, false, false, false, false, false, false, []string{})
cmdutil.CheckErr(err) cmdutil.CheckErr(err)
if p != nil { if p != nil {
validArgs = p.HandledResources() validArgs = p.HandledResources()
argAliases = kubectl.ResourceAliases(validArgs)
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -89,7 +90,8 @@ func NewCmdLabel(f *cmdutil.Factory, out io.Writer) *cobra.Command {
err := RunLabel(f, out, cmd, args, options) err := RunLabel(f, out, cmd, args, options)
cmdutil.CheckErr(err) cmdutil.CheckErr(err)
}, },
ValidArgs: validArgs, ValidArgs: validArgs,
ArgAliases: argAliases,
} }
cmdutil.AddPrinterFlags(cmd) cmdutil.AddPrinterFlags(cmd)
cmd.Flags().Bool("overwrite", false, "If true, allow labels to be overwritten, otherwise reject label updates that overwrite existing labels.") cmd.Flags().Bool("overwrite", false, "If true, allow labels to be overwritten, otherwise reject label updates that overwrite existing labels.")

View File

@ -175,6 +175,35 @@ func expandResourceShortcut(resource unversioned.GroupVersionResource) unversion
return resource return resource
} }
// ResourceAliases returns the resource shortcuts and plural forms for the given resources.
func ResourceAliases(rs []string) []string {
as := make([]string, 0, len(rs))
plurals := make(map[string]struct{}, len(rs))
for _, r := range rs {
var plural string
switch {
case r == "endpoints":
plural = r // exception. "endpoint" does not exist. Why?
case strings.HasSuffix(r, "y"):
plural = r[0:len(r)-1] + "ies"
case strings.HasSuffix(r, "s"):
plural = r + "es"
default:
plural = r + "s"
}
as = append(as, plural)
plurals[plural] = struct{}{}
}
for sf, r := range shortForms {
if _, found := plurals[r]; found {
as = append(as, sf)
}
}
return as
}
// parseFileSource parses the source given. Acceptable formats include: // parseFileSource parses the source given. Acceptable formats include:
// //
// 1. source-path: the basename will become the key name // 1. source-path: the basename will become the key name