Golang中如何在子命令前使用Cobra框架处理可变参数

Golang中如何在子命令前使用Cobra框架处理可变参数 我是一名Go语言新手,尽管我已经用其他语言编写代码几十年了。

我正在考虑编写类似“kubectl”的工具,但其中总是有一个代表上下文的首个参数,然后是子命令,接着是选项标志。

也有可能只存在上下文名称,没有子命令,这将代表一个“隐含”的子命令,用于打印给定上下文名称作为子字符串的上下文名称列表。

我不确定如何使用Cobra构建类似这样的东西。是否有合理的方法来修改默认结构以实现此目的?

1 回复

更多关于Golang中如何在子命令前使用Cobra框架处理可变参数的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Cobra框架中处理可变参数并实现类似kubectl的上下文参数模式,可以通过自定义参数验证和命令执行流程来实现。以下是一个实现示例:

package main

import (
    "fmt"
    "os"
    "strings"

    "github.com/spf13/cobra"
)

var contexts = []string{"prod-cluster", "staging-cluster", "dev-cluster", "test-cluster"}

func main() {
    var rootCmd = &cobra.Command{
        Use:   "myctl",
        Short: "A kubectl-like tool with context parameter",
        // 禁用默认的参数验证,我们将自定义处理
        Args: cobra.ArbitraryArgs,
        RunE: func(cmd *cobra.Command, args []string) error {
            // 处理没有子命令的情况
            if len(args) == 0 {
                // 显示所有上下文
                fmt.Println("Available contexts:")
                for _, ctx := range contexts {
                    fmt.Printf("  %s\n", ctx)
                }
                return nil
            }

            // 第一个参数是上下文
            context := args[0]
            
            // 如果没有子命令,执行隐式子命令
            if len(args) == 1 {
                // 查找匹配的上下文
                fmt.Printf("Contexts matching '%s':\n", context)
                for _, ctx := range contexts {
                    if strings.Contains(ctx, context) {
                        fmt.Printf("  %s\n", ctx)
                    }
                }
                return nil
            }

            // 如果有子命令,将上下文传递给子命令
            // 这里需要手动执行子命令
            cmd.SilenceUsage = true
            return fmt.Errorf("use 'myctl %s [command]'", context)
        },
    }

    // 添加子命令
    var getCmd = &cobra.Command{
        Use:   "get [resource]",
        Short: "Get resources",
        Run: func(cmd *cobra.Command, args []string) {
            // 上下文通过父命令的PersistentFlags获取
            context, _ := cmd.Parent().PersistentFlags().GetString("context")
            fmt.Printf("Getting resources in context: %s\n", context)
            fmt.Printf("Resource: %v\n", args)
        },
    }

    var describeCmd = &cobra.Command{
        Use:   "describe [resource]",
        Short: "Describe resource",
        Run: func(cmd *cobra.Command, args []string) {
            context, _ := cmd.Parent().PersistentFlags().GetString("context")
            fmt.Printf("Describing resource in context: %s\n", context)
            fmt.Printf("Resource: %v\n", args)
        },
    }

    // 创建带上下文的命令包装器
    for _, ctx := range contexts {
        ctxCmd := &cobra.Command{
            Use:   ctx,
            Short: fmt.Sprintf("Commands for context %s", ctx),
            PersistentPreRun: func(cmd *cobra.Command, args []string) {
                // 设置上下文标志
                cmd.PersistentFlags().Set("context", cmd.Name())
            },
        }

        // 为每个上下文添加子命令
        ctxCmd.AddCommand(getCmd, describeCmd)
        
        // 添加上下文特定的标志
        ctxCmd.PersistentFlags().String("namespace", "default", "Namespace for this context")
        
        rootCmd.AddCommand(ctxCmd)
    }

    // 添加全局标志
    rootCmd.PersistentFlags().String("context", "", "Context to use")
    rootCmd.PersistentFlags().Bool("verbose", false, "Verbose output")

    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

另一个更灵活的实现方式,使用中间件模式处理上下文参数:

package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

type ContextAwareCommand struct {
    *cobra.Command
    context string
}

func NewContextAwareCommand() *ContextAwareCommand {
    cmd := &cobra.Command{
        Use:   "myctl [context] [command]",
        Short: "Context-aware CLI tool",
        Args:  cobra.MinimumNArgs(1),
        PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
            // 保存上下文到命令的注解中
            if len(args) > 0 {
                cmd.Annotations["context"] = args[0]
            }
            return nil
        },
    }

    return &ContextAwareCommand{
        Command: cmd,
    }
}

func (c *ContextAwareCommand) AddContextCommand(context string) *cobra.Command {
    ctxCmd := &cobra.Command{
        Use:   context,
        Short: fmt.Sprintf("Commands for %s context", context),
        Args:  cobra.ArbitraryArgs,
        Run: func(cmd *cobra.Command, args []string) {
            if len(args) == 0 {
                fmt.Printf("Available commands for context '%s':\n", context)
                for _, subCmd := range cmd.Commands() {
                    if !subCmd.Hidden {
                        fmt.Printf("  %-15s %s\n", subCmd.Name(), subCmd.Short)
                    }
                }
                return
            }
            cmd.Help()
        },
    }

    // 添加子命令到上下文命令
    getCmd := &cobra.Command{
        Use:   "get [resource]",
        Short: "Get resources",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("Getting resources in context: %s\n", context)
            if len(args) > 0 {
                fmt.Printf("Resource: %s\n", args[0])
            }
        },
    }

    logsCmd := &cobra.Command{
        Use:   "logs [pod]",
        Short: "Get pod logs",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("Getting logs in context: %s\n", context)
            if len(args) > 0 {
                fmt.Printf("Pod: %s\n", args[0])
            }
        },
    }

    ctxCmd.AddCommand(getCmd, logsCmd)
    c.AddCommand(ctxCmd)
    return ctxCmd
}

func main() {
    rootCmd := NewContextAwareCommand()

    // 添加上下文命令
    contexts := []string{"prod", "staging", "dev"}
    for _, ctx := range contexts {
        rootCmd.AddContextCommand(ctx)
    }

    // 添加全局命令(不需要上下文)
    versionCmd := &cobra.Command{
        Use:   "version",
        Short: "Print version",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("myctl v1.0.0")
        },
    }
    rootCmd.AddCommand(versionCmd)

    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

这个实现提供了:

  1. 第一个参数作为上下文处理
  2. 支持隐式子命令(当只有上下文参数时)
  3. 上下文特定的子命令结构
  4. 通过PersistentFlags在命令间传递上下文

执行示例:

# 显示所有上下文
$ myctl

# 查找匹配的上下文
$ myctl prod

# 在特定上下文中执行命令
$ myctl prod get pods
$ myctl staging logs my-app
回到顶部