Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add dynamic completion support for context in kubeconfig and namespace #5094

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cmd/clusterctl/client/cluster/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,12 @@ type Proxy interface {

// ListResources returns all the Kubernetes objects with the given labels existing the listed namespaces.
ListResources(labels map[string]string, namespaces ...string) ([]unstructured.Unstructured, error)

// GetContexts returns the list of contexts in kubeconfig which begin with prefix.
GetContexts(prefix string) ([]string, error)

// GetResourceNames returns the list of resource names which begin with prefix.
GetResourceNames(groupVersion, kind string, options []client.ListOption, prefix string) ([]string, error)
}

// retryWithExponentialBackoff repeats an operation until it passes or the exponential backoff times out.
Expand Down
41 changes: 41 additions & 0 deletions cmd/clusterctl/client/cluster/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,47 @@ func (k *proxy) ListResources(labels map[string]string, namespaces ...string) ([
return ret, nil
}

// GetContexts returns the list of contexts in kubeconfig which begin with prefix.
func (k *proxy) GetContexts(prefix string) ([]string, error) {
config, err := k.configLoadingRules.Load()
if err != nil {
return nil, err
}

var comps []string
for name := range config.Contexts {
if strings.HasPrefix(name, prefix) {
comps = append(comps, name)
}
}

return comps, nil
}

// GetResourceNames returns the list of resource names which begin with prefix.
func (k *proxy) GetResourceNames(groupVersion, kind string, options []client.ListOption, prefix string) ([]string, error) {
client, err := k.NewClient()
if err != nil {
return nil, err
}

objList, err := listObjByGVK(client, groupVersion, kind, options)
if err != nil {
return nil, err
}

var comps []string
for _, item := range objList.Items {
name := item.GetName()

if strings.HasPrefix(name, prefix) {
comps = append(comps, name)
}
}

return comps, nil
}

func listObjByGVK(c client.Client, groupVersion, kind string, options []client.ListOption) (*unstructured.UnstructuredList, error) {
objList := new(unstructured.UnstructuredList)
objList.SetAPIVersion(groupVersion)
Expand Down
41 changes: 41 additions & 0 deletions cmd/clusterctl/cmd/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"os"

"github.com/spf13/cobra"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
)

const completionBoilerPlate = `# Copyright 2021 The Kubernetes Authors.
Expand Down Expand Up @@ -138,3 +140,42 @@ func runCompletionZsh(out io.Writer, cmd *cobra.Command) error {

return nil
}

func contextCompletionFunc(kubeconfig *string) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
configClient, err := config.New(cfgFile)
if err != nil {
return completionError(err)
}

client := cluster.New(cluster.Kubeconfig{Path: *kubeconfig}, configClient)
comps, err := client.Proxy().GetContexts(toComplete)
if err != nil {
return completionError(err)
}

return comps, cobra.ShellCompDirectiveNoFileComp
}
}

func resourceNameCompletionFunc(kubeconfig *string, groupVersion, kind string) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
configClient, err := config.New(cfgFile)
if err != nil {
return completionError(err)
}

client := cluster.New(cluster.Kubeconfig{Path: *kubeconfig}, configClient)
comps, err := client.Proxy().GetResourceNames(groupVersion, kind, nil, toComplete)
if err != nil {
return completionError(err)
}

return comps, cobra.ShellCompDirectiveNoFileComp
}
}

func completionError(err error) ([]string, cobra.ShellCompDirective) {
cobra.CompError(err.Error())
return nil, cobra.ShellCompDirectiveError
}
20 changes: 19 additions & 1 deletion cmd/clusterctl/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func init() {
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "",
"Path to clusterctl configuration (default is `$HOME/.cluster-api/clusterctl.yaml`) or to a remote location (i.e. https://example.com/clusterctl.yaml)")

cobra.OnInitialize(initConfig)
cobra.OnInitialize(initConfig, registerCompletionFuncForCommonFlags)
}

func initConfig() {
Expand All @@ -135,6 +135,24 @@ func initConfig() {
logf.SetLogger(logf.NewLogger(logf.WithThreshold(verbosity)))
}

func registerCompletionFuncForCommonFlags() {
visitCommands(RootCmd, func(cmd *cobra.Command) {
if f := cmd.Flags().Lookup("kubeconfig"); f != nil {
kubeconfig := f.Value.String()

// context in kubeconfig
for _, flagName := range []string{"kubeconfig-context", "to-kubeconfig-context"} {
_ = cmd.RegisterFlagCompletionFunc(flagName, contextCompletionFunc(&kubeconfig))
}

// namespace
for _, flagName := range []string{"namespace", "target-namespace", "from-config-map-namespace"} {
_ = cmd.RegisterFlagCompletionFunc(flagName, resourceNameCompletionFunc(&kubeconfig, "v1", "namespace"))
}
}
})
}

const indentation = ` `

// LongDesc normalizes a command's long description to follow the conventions.
Expand Down
10 changes: 10 additions & 0 deletions cmd/clusterctl/cmd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"text/tabwriter"

"github.com/pkg/errors"
"github.com/spf13/cobra"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client"
)

Expand Down Expand Up @@ -166,3 +167,12 @@ func printComponentsAsText(c client.Components) error {

return nil
}

// visitCommands visits the commands.
func visitCommands(cmd *cobra.Command, fn func(*cobra.Command)) {
fn(cmd)

for _, c := range cmd.Commands() {
visitCommands(c, fn)
}
}
8 changes: 8 additions & 0 deletions cmd/clusterctl/internal/test/fake_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ func (f *FakeProxy) ListResources(labels map[string]string, namespaces ...string
return ret, nil
}

func (f *FakeProxy) GetContexts(prefix string) ([]string, error) {
return nil, nil
}

func (f *FakeProxy) GetResourceNames(groupVersion, kind string, options []client.ListOption, prefix string) ([]string, error) {
return nil, nil
}

func NewFakeProxy() *FakeProxy {
return &FakeProxy{
namespace: "default",
Expand Down