Skip to content

Commit

Permalink
feat: autocomplete nodes, context and resource definitions
Browse files Browse the repository at this point in the history
Makes `talosctl` autocomplete the most used dynamic positional parameters like resource definitions, IDs of resource definitions, and also values for arguments like `--nodes` and `--context`.

Signed-off-by: Nico Berlee <nico.berlee@on2it.net>
Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
  • Loading branch information
nberlee authored and smira committed Dec 6, 2021
1 parent b4b3e21 commit dc9db21
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 7 deletions.
3 changes: 3 additions & 0 deletions cmd/talosctl/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/talos-systems/talos/cmd/talosctl/cmd/mgmt"
"github.com/talos-systems/talos/cmd/talosctl/cmd/talos"
"github.com/talos-systems/talos/pkg/cli"
clientconfig "github.com/talos-systems/talos/pkg/machinery/client/config"
)

Expand All @@ -38,6 +39,8 @@ func Execute() error {
rootCmd.PersistentFlags().StringVar(&talos.Cmdcontext, "context", "", "Context to be used in command")
rootCmd.PersistentFlags().StringSliceVarP(&talos.Nodes, "nodes", "n", []string{}, "target the specified nodes")
rootCmd.PersistentFlags().StringSliceVarP(&talos.Endpoints, "endpoints", "e", []string{}, "override default endpoints in Talos configuration")
cli.Should(rootCmd.RegisterFlagCompletionFunc("context", talos.CompleteConfigContext))
cli.Should(rootCmd.RegisterFlagCompletionFunc("nodes", talos.CompleteNodes))

cmd, err := rootCmd.ExecuteC()
if err != nil {
Expand Down
18 changes: 18 additions & 0 deletions cmd/talosctl/cmd/talos/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ var configContextCmd = &cobra.Command{

return nil
},
ValidArgsFunction: CompleteConfigContext,
}

// configAddCmdFlags represents the `config add` command flags.
Expand Down Expand Up @@ -410,6 +411,23 @@ var configInfoCmd = &cobra.Command{
},
}

// CompleteConfigContext represents tab completion for `--context` argument and `config context` command.
func CompleteConfigContext(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
c, err := clientconfig.Open(Talosconfig)
if err != nil {
return nil, cobra.ShellCompDirectiveError
}

contextnames := make([]string, 0, len(c.Contexts))
for contextname := range c.Contexts {
contextnames = append(contextnames, contextname)
}

sort.Strings(contextnames)

return contextnames, cobra.ShellCompDirectiveNoFileComp
}

func init() {
configCmd.AddCommand(
configEndpointCmd,
Expand Down
175 changes: 169 additions & 6 deletions cmd/talosctl/cmd/talos/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ import (
"fmt"
"io"
"os"
"strings"

"github.com/cosi-project/runtime/pkg/resource/meta"
"github.com/spf13/cobra"
"google.golang.org/grpc/codes"
yaml "gopkg.in/yaml.v3"

"github.com/talos-systems/talos/cmd/talosctl/cmd/talos/output"
"github.com/talos-systems/talos/cmd/talosctl/pkg/talos/helpers"
"github.com/talos-systems/talos/pkg/cli"
"github.com/talos-systems/talos/pkg/machinery/client"
"github.com/talos-systems/talos/pkg/machinery/resources/cluster"
)

var getCmdFlags struct {
Expand All @@ -28,11 +33,28 @@ var getCmdFlags struct {

// getCmd represents the get (resources) command.
var getCmd = &cobra.Command{
Use: "get <type> [<id>]",
Aliases: []string{"g"},
Short: "Get a specific resource or list of resources.",
Long: `Similar to 'kubectl get', 'talosctl get' returns a set of resources from the OS. To get a list of all available resource definitions, issue 'talosctl get rd'`,
Args: cobra.RangeArgs(1, 2),
Use: "get <type> [<id>]",
Aliases: []string{"g"},
SuggestFor: []string{},
Short: "Get a specific resource or list of resources.",
Long: `Similar to 'kubectl get', 'talosctl get' returns a set of resources from the OS. To get a list of all available resource definitions, issue 'talosctl get rd'`,
Example: "",
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
switch len(args) {
case 0:
if toComplete == "" {
return completeResource(meta.ResourceDefinitionType, true, false), cobra.ShellCompDirectiveNoFileComp
}

return completeResource(meta.ResourceDefinitionType, true, true), cobra.ShellCompDirectiveNoFileComp
case 1:

return completeResource(args[0], false, true), cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace
}

return nil, cobra.ShellCompDirectiveError | cobra.ShellCompDirectiveNoFileComp
},
Args: cobra.RangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
if getCmdFlags.insecure {
return WithClientMaintenance(nil, getResources(args))
Expand Down Expand Up @@ -128,10 +150,151 @@ func getResources(args []string) func(ctx context.Context, c *client.Client) err
}
}

//nolint:gocyclo
func getResourcesResponse(args []string, clientmsg *[]client.ResourceResponse) func(ctx context.Context, c *client.Client) error {
return func(ctx context.Context, c *client.Client) error {
var resourceID string

resourceType := args[0]
namespace := getCmdFlags.namespace

if len(args) > 1 {
resourceID = args[1]
}

if resourceID != "" {
resp, err := c.Resources.Get(ctx, namespace, resourceType, resourceID)
if err != nil {
return err
}

for _, msg := range resp {
if msg.Resource == nil {
continue
}

*clientmsg = append(*clientmsg, msg)
}
} else {
listClient, err := c.Resources.List(ctx, namespace, resourceType)
if err != nil {
return err
}

for {
msg, err := listClient.Recv()
if err != nil {
if err == io.EOF || client.StatusCode(err) == codes.Canceled {
return nil
}

return err
}

if msg.Metadata.GetError() != "" {
fmt.Fprintf(os.Stderr, "%s: %s\n", msg.Metadata.GetHostname(), msg.Metadata.GetError())

continue
}
if msg.Resource == nil {
continue
}
*clientmsg = append(*clientmsg, msg)
}
}

return nil
}
}

//nolint:gocyclo
// completeResource represents tab complete options for `get` and `get *` commands.
func completeResource(resourceType string, hasAliasses bool, completeDot bool) []string {
var (
resourceResponse []client.ResourceResponse
resourceOptions []string
)

if WithClient(getResourcesResponse([]string{resourceType}, &resourceResponse)) != nil {
return nil
}

for _, msg := range resourceResponse {
if completeDot {
resourceOptions = append(resourceOptions, msg.Resource.Metadata().ID())
}

if !hasAliasses {
continue
}

resourceSpec, err := yaml.Marshal(msg.Resource.Spec())
if err != nil {
continue
}

var resourceSpecRaw map[string]interface{}

if yaml.Unmarshal(resourceSpec, &resourceSpecRaw) != nil {
continue
}

if aliasSlice, ok := resourceSpecRaw["aliases"].([]interface{}); ok {
for _, alias := range aliasSlice {
if !completeDot && strings.Contains(alias.(string), ".") {
continue
}

resourceOptions = append(resourceOptions, alias.(string))
}
}
}

return resourceOptions
}

// CompleteNodes represents tab completion for `--nodes` argument.
func CompleteNodes(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
var (
resourceResponse []client.ResourceResponse
nodes []string
)

if WithClientNoNodes(getResourcesResponse([]string{cluster.MemberType}, &resourceResponse)) != nil {
return nil, cobra.ShellCompDirectiveError
}

for _, msg := range resourceResponse {
var resourceSpecRaw map[string]interface{}

resourceSpec, err := yaml.Marshal(msg.Resource.Spec())
if err != nil {
continue
}

if err = yaml.Unmarshal(resourceSpec, &resourceSpecRaw); err != nil {
continue
}

if hostname, ok := resourceSpecRaw["hostname"].(string); ok {
nodes = append(nodes, hostname)
}

if addressSlice, ok := resourceSpecRaw["addresses"].([]interface{}); ok {
for _, address := range addressSlice {
nodes = append(nodes, address.(string))
}
}
}

return nodes, cobra.ShellCompDirectiveNoFileComp
}

func init() {
getCmd.Flags().StringVar(&getCmdFlags.namespace, "namespace", "", "resource namespace (default is to use default namespace per resource)")
getCmd.Flags().StringVarP(&getCmdFlags.output, "output", "o", "table", "output mode (table, yaml)")
getCmd.Flags().StringVarP(&getCmdFlags.output, "output", "o", "table", "output mode (json, table, yaml)")
getCmd.Flags().BoolVarP(&getCmdFlags.watch, "watch", "w", false, "watch resource changes")
getCmd.Flags().BoolVarP(&getCmdFlags.insecure, "insecure", "i", false, "get resources using the insecure (encrypted with no auth) maintenance service")
cli.Should(getCmd.RegisterFlagCompletionFunc("output", output.CompleteOutputArg))
addCommand(getCmd)
}
6 changes: 6 additions & 0 deletions cmd/talosctl/cmd/talos/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/state"
"github.com/spf13/cobra"
)

// Writer interface.
Expand All @@ -32,3 +33,8 @@ func NewWriter(format string) (Writer, error) {
return nil, fmt.Errorf("output format %q is not supported", format)
}
}

// CompleteOutputArg represents tab completion for `--output` argument.
func CompleteOutputArg(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"json", "table", "yaml"}, cobra.ShellCompDirectiveNoFileComp
}
1 change: 1 addition & 0 deletions cmd/talosctl/cmd/talos/support.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ var supportCmd = &cobra.Command{
- Kubernetes nodes and kube-system pods manifests.
`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if len(Nodes) == 0 {
return fmt.Errorf("please provide at least a single node to gather the debug information from")
Expand Down
2 changes: 1 addition & 1 deletion website/content/docs/v0.14/Reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1280,7 +1280,7 @@ talosctl get <type> [<id>] [flags]
-h, --help help for get
-i, --insecure get resources using the insecure (encrypted with no auth) maintenance service
--namespace string resource namespace (default is to use default namespace per resource)
-o, --output string output mode (table, yaml) (default "table")
-o, --output string output mode (json, table, yaml) (default "table")
-w, --watch watch resource changes
```

Expand Down

0 comments on commit dc9db21

Please sign in to comment.