From 26a5aa179b72268ec2fb3867fc444510822bc837 Mon Sep 17 00:00:00 2001 From: John Gardiner Myers Date: Sun, 27 Jun 2021 21:32:16 -0700 Subject: [PATCH] Improve completion for kops root command --- cmd/kops/get.go | 3 +- cmd/kops/get_cluster.go | 3 +- cmd/kops/root.go | 14 ++++---- pkg/commands/commandutils/BUILD.bazel | 14 +++++++- pkg/commands/commandutils/cluster.go | 48 +++++++++++++++++++++++++++ pkg/commands/commandutils/error.go | 29 ++++++++++++++++ pkg/commands/commandutils/factory.go | 23 +++++++++++++ pkg/commands/commandutils/klog.go | 34 +++++++++++++++++++ 8 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 pkg/commands/commandutils/cluster.go create mode 100644 pkg/commands/commandutils/error.go create mode 100644 pkg/commands/commandutils/factory.go create mode 100644 pkg/commands/commandutils/klog.go diff --git a/cmd/kops/get.go b/cmd/kops/get.go index 00d7bd6a531b1..c80db96cebfa5 100644 --- a/cmd/kops/get.go +++ b/cmd/kops/get.go @@ -28,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/kops/cmd/kops/util" api "k8s.io/kops/pkg/apis/kops" + "k8s.io/kops/pkg/commands/commandutils" "k8s.io/kops/pkg/kopscodecs" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" @@ -116,7 +117,7 @@ func NewCmdGet(f *util.Factory, out io.Writer) *cobra.Command { return cmd } -func RunGet(ctx context.Context, f Factory, out io.Writer, options *GetOptions) error { +func RunGet(ctx context.Context, f commandutils.Factory, out io.Writer, options *GetOptions) error { client, err := f.Clientset() if err != nil { diff --git a/cmd/kops/get_cluster.go b/cmd/kops/get_cluster.go index 1fc1d596e3717..0da02bd7e39e0 100644 --- a/cmd/kops/get_cluster.go +++ b/cmd/kops/get_cluster.go @@ -31,6 +31,7 @@ import ( "k8s.io/kops/cmd/kops/util" kopsapi "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops/registry" + "k8s.io/kops/pkg/commands/commandutils" "k8s.io/kops/pkg/kopscodecs" "k8s.io/kops/util/pkg/tables" "k8s.io/kubectl/pkg/util/i18n" @@ -120,7 +121,7 @@ func NewCmdGetCluster(f *util.Factory, out io.Writer, getOptions *GetOptions) *c return cmd } -func RunGetClusters(ctx context.Context, f Factory, out io.Writer, options *GetClusterOptions) error { +func RunGetClusters(ctx context.Context, f commandutils.Factory, out io.Writer, options *GetClusterOptions) error { client, err := f.Clientset() if err != nil { return err diff --git a/cmd/kops/root.go b/cmd/kops/root.go index 67025f5bdc39f..461a36667f2df 100644 --- a/cmd/kops/root.go +++ b/cmd/kops/root.go @@ -36,6 +36,7 @@ import ( kopsapi "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/client/simple" "k8s.io/kops/pkg/commands" + "k8s.io/kops/pkg/commands/commandutils" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -65,10 +66,6 @@ var ( rootShort = i18n.T(`kOps is Kubernetes Operations.`) ) -type Factory interface { - Clientset() (simple.Clientset, error) -} - type RootCmd struct { util.FactoryOptions @@ -81,7 +78,7 @@ type RootCmd struct { cobraCommand *cobra.Command } -var _ Factory = &RootCmd{} +var _ commandutils.Factory = &RootCmd{} var rootCommand = RootCmd{ cobraCommand: &cobra.Command{ @@ -129,13 +126,18 @@ func NewCmdRoot(f *util.Factory, out io.Writer) *cobra.Command { cmd.PersistentFlags().StringVar(&rootCommand.configFile, "config", "", "yaml config file (default is $HOME/.kops.yaml)") viper.BindPFlag("config", cmd.PersistentFlags().Lookup("config")) viper.SetDefault("config", "$HOME/.kops.yaml") + cmd.RegisterFlagCompletionFunc("config", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"yaml", "json"}, cobra.ShellCompDirectiveFilterFileExt + }) cmd.PersistentFlags().StringVar(&rootCommand.RegistryPath, "state", "", "Location of state storage (kops 'config' file). Overrides KOPS_STATE_STORE environment variable") viper.BindPFlag("KOPS_STATE_STORE", cmd.PersistentFlags().Lookup("state")) viper.BindEnv("KOPS_STATE_STORE") + // TODO implement completion against VFS defaultClusterName := os.Getenv("KOPS_CLUSTER_NAME") cmd.PersistentFlags().StringVarP(&rootCommand.clusterName, "name", "", defaultClusterName, "Name of cluster. Overrides KOPS_CLUSTER_NAME environment variable") + cmd.RegisterFlagCompletionFunc("name", commandutils.CompleteClusterName(&rootCommand)) // create subcommands cmd.AddCommand(NewCmdCompletion(f, out)) @@ -290,7 +292,7 @@ func (c *RootCmd) Cluster(ctx context.Context) (*kopsapi.Cluster, error) { return GetCluster(ctx, c.factory, clusterName) } -func GetCluster(ctx context.Context, factory Factory, clusterName string) (*kopsapi.Cluster, error) { +func GetCluster(ctx context.Context, factory commandutils.Factory, clusterName string) (*kopsapi.Cluster, error) { if clusterName == "" { return nil, field.Required(field.NewPath("clusterName"), "Cluster name is required") } diff --git a/pkg/commands/commandutils/BUILD.bazel b/pkg/commands/commandutils/BUILD.bazel index 1b96ee4145154..28cdd63ba5d6c 100644 --- a/pkg/commands/commandutils/BUILD.bazel +++ b/pkg/commands/commandutils/BUILD.bazel @@ -2,7 +2,19 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", - srcs = ["exit.go"], + srcs = [ + "cluster.go", + "error.go", + "exit.go", + "factory.go", + "klog.go", + ], importpath = "k8s.io/kops/pkg/commands/commandutils", visibility = ["//visibility:public"], + deps = [ + "//pkg/client/simple:go_default_library", + "//vendor/github.com/spf13/cobra:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/klog/v2:go_default_library", + ], ) diff --git a/pkg/commands/commandutils/cluster.go b/pkg/commands/commandutils/cluster.go new file mode 100644 index 0000000000000..e70a44a8c9b96 --- /dev/null +++ b/pkg/commands/commandutils/cluster.go @@ -0,0 +1,48 @@ +/* +Copyright 2021 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 commandutils + +import ( + "context" + + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// CompleteClusterName returns a Cobra completion function for cluster names. +func CompleteClusterName(f Factory) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ConfigureKlogForCompletion() + + client, err := f.Clientset() + if err != nil { + return CompletionError("getting clientset", err) + } + + list, err := client.ListClusters(context.TODO(), metav1.ListOptions{}) + if err != nil { + return CompletionError("listing clusters", err) + } + + var clusterNames []string + for _, cluster := range list.Items { + clusterNames = append(clusterNames, cluster.Name) + } + + return clusterNames, cobra.ShellCompDirectiveNoFileComp + } +} diff --git a/pkg/commands/commandutils/error.go b/pkg/commands/commandutils/error.go new file mode 100644 index 0000000000000..2d7c304a0a895 --- /dev/null +++ b/pkg/commands/commandutils/error.go @@ -0,0 +1,29 @@ +/* +Copyright 2021 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 commandutils + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// CompletionError a helper function to logs and return an error from a Cobra completion function. +func CompletionError(prefix string, err error) ([]string, cobra.ShellCompDirective) { + cobra.CompError(fmt.Sprintf("%s: %v", prefix, err)) + return nil, cobra.ShellCompDirectiveError | cobra.ShellCompDirectiveNoFileComp +} diff --git a/pkg/commands/commandutils/factory.go b/pkg/commands/commandutils/factory.go new file mode 100644 index 0000000000000..a8e7ed42f314f --- /dev/null +++ b/pkg/commands/commandutils/factory.go @@ -0,0 +1,23 @@ +/* +Copyright 2021 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 commandutils + +import "k8s.io/kops/pkg/client/simple" + +type Factory interface { + Clientset() (simple.Clientset, error) +} diff --git a/pkg/commands/commandutils/klog.go b/pkg/commands/commandutils/klog.go new file mode 100644 index 0000000000000..2d31f3242eb0b --- /dev/null +++ b/pkg/commands/commandutils/klog.go @@ -0,0 +1,34 @@ +/* +Copyright 2021 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 commandutils + +import ( + "github.com/spf13/cobra" + "k8s.io/klog/v2" +) + +// ConfigureKlogForCompletion configures Klog to not interfere with Cobra completion functions. +func ConfigureKlogForCompletion() { + klog.SetOutput(&toCompDebug{}) +} + +type toCompDebug struct{} + +func (t toCompDebug) Write(p []byte) (n int, err error) { + cobra.CompDebug(string(p), false) + return len(p), nil +}