Golang Map YAML对象

时间:2018-07-10 17:05:49

标签: dictionary go configuration

我有一个yaml配置文件,其中包含要发送到网络设备的多组配置命令。在每个集合的内部,有特定于供应商的键,每个供应商键的值可以是配置命令字符串,配置命令字符串列表或将特定于供应商的模型字符串映射到密钥的键值对列表。配置命令字符串。下面是一个示例:

# example.yml
---
cmds:
  setup:
    cisco: "terminal length 0"
  config:
    cisco:
      - basic  : "show version"
      - basic  : "show boot"
        "3560" : "3560 boot command"
        "2960x": "2960x boot command"
      - basic  : "dir flash:"
        "3560" : "3560 dir command"
  cleanup:
    cisco: ["terminal no length", "quit"]

我想将这些命令组合成一个地图,如下所示:

var cmdMap = map[string][]string{
    "cisco": []string{
        "terminal length 0",
        "show version",
        "show boot",
        "dir flash:",
        "terminal no length",
        "quit",
    },
    "cisco.3560": []string{
        "terminal length 0",
        "show version",
        "3560 boot command",
        "3560 dir command",
        "terminal no length",
        "quit",
    },
    "cisco.2960x": []string{
        "terminal length 0",
        "show version",
        "2960x boot command",
        "dir flash:",
        "terminal no length",
        "quit",
    }
}

我正在使用spf13/viper来处理yaml文件的解析,并且能够向每个供应商和模型添加特定的命令,但是添加了适用于特定于供应商的命令模特是我被困的地方。这是程序的实际输出,后面是代码:

$ go run main.go example.yml 
cmdMap["cisco"]
terminal length 0
show version
show boot
dir flash:
terminal no length
quit

# missing terminal length 0, show version, etc.
cmdMap["cisco.3560"]
3560 boot command
3560 dir command

# missing terminal length 0, show version, etc.
cmdMap["cisco.2960x"]
2960x boot command

我的代码:

package main

import (
    "github.com/spf13/viper"
    "fmt"
    "flag"
    "log"
)

func main() {
    flag.Parse()
    cfgFile := flag.Arg(0)

    v := viper.New()
    v.SetConfigType("yaml")
    v.SetConfigFile(cfgFile)

    if err := v.ReadInConfig(); err != nil {
        log.Fatal(err)
    }

    for k, v := range MapCfgCmds(v.GetStringMap("cmds")) {
        fmt.Printf("cmdMap[\"%s\"]\n", k)
        for _, cmd := range v {
            fmt.Println(cmd)
        }
        fmt.Println()
    }
}

func MapCfgCmds(cfgCmds map[string]interface{}) map[string][]string {
    cmdMap := make(map[string][]string)
    for _, cmdSet := range cfgCmds {
        cmdSet, _ := cmdSet.(map[string]interface{})
        for vendor, cmdList := range cmdSet {
            switch cmds := cmdList.(type) {
            case string:
                // single string command (i.e., vendor: cmd)
                cmdMap[vendor] = append(cmdMap[vendor], cmds)
            case []interface{}:
                for _, cmd := range cmds {
                    switch c := cmd.(type) {
                    case string:
                        // list of strings (i.e., vendor: [cmd1,cmd2,...,cmdN])
                        cmdMap[vendor] = append(cmdMap[vendor], c)
                    case map[interface{}]interface{}:
                        // This is where I am stuck
                        //
                        // list of key-value pairs (i.e., vendor: {model: modelCmd})
                        for model, modelCmd := range c {
                            modelCmd, _ := modelCmd.(string)
                            if model == "basic" {
                                cmdMap[vendor] = append(cmdMap[vendor], modelCmd)
                                continue
                            }
                            modelKey := fmt.Sprintf("%s.%s", vendor, model)
                            cmdMap[modelKey] = append(cmdMap[modelKey], modelCmd)
                        }
                    }
                }
            }
        }
    }
    return cmdMap
}

如何结合使用“通用”命令和特定于模型的命令以从上方获取预期的cmdMap值?

2 个答案:

答案 0 :(得分:1)

从某种意义上说,viper不能为您提供帮助,因为viper可以完成许多您不需要的事情,但是它并不能完成您可以在此处使用的一件事-清晰地映射数据。如果直接使用yaml library,则可以声明一个与您的数据相对应的结构,以便于理解。

有几种方法可以解决您的问题,这是我尝试解决的方法(您可能需要调整一些我在编辑器中编写的内容,而无需对其进行编译):

    type Data struct {
       Cmds struct {
         Setup map[string]interface{} `yaml:"setup"`
         Config map[string][]map[string]string `yaml:"config"`
         Cleanup map[string][]string `yaml:"cleanup"`
       } `yaml:"cmds"`

    }   

    data := Data{}
    err := yaml.Unmarshal([]byte(input), &data)
    if err != nil {
            log.Fatalf("error: %v", err)
    }

    setupCmds := make(map[string][]string)
    cleanupCmds := make(map[string][]string)

    result := make(map[string][]string)


    // Prepare setup commands, grouped by vendor
    for vendor, setupCmd := range data.Cmds.Setup {
        setupCmds[vendor] = append(setupCmds[vendor], setupCmd)
    }

    // Prepare cleanup commands, grouped by vendor
     for vendor, commands := range data.Cmds.Cleanup {
        cleanupCmds[vendor] = append(cleanupCmds[vendor], commands...)
    }

    // iterate over vendors and models, combine with setup & cleanup commands and store in result
    for vendor, configCmds := range data.Cmds.Config { // vendor = string (e.g. "cisco"), configCmds = []map[string][string] (e.g. - basic: "show version")
        // we now how many config commands there will be
        result[vendor] = make([]string, len(configCmds))

        // variantsCache will store all variants we've seen so far
        variantsCache := make(map[string]struct{})
        for i, model := range models { // i = int (number of command) model = map[string]string
            // we assume "basic" is available for each command
            result[vendor][i] = model["basic"]
            for variant, command := range model { // variant = string (e.g. "basic"), command = string (e.g. "show version")
                if variant == "basic" {
                    // we already covered that
                    continue
                }
                variantKey := vendor + "." + variant
                variantsCache[variantKey]
                if _, ok := result[variantKey]; !ok {
                    // first command for this model, create a slice
                    result[variantKey] = make([]string, len(configCmds))
                }
                result[variantKey][i] = command
            }
        }
        // We need to iterate over all commands for all variants and copy "basic" command if there is none
        for variant, _ := range variantsCache {
            for i, command := range result[variant] {
                if command == "" {
                    // copy the "basic" command, since there was no variant specific command
                    result[variant][i] = result[vendor][i]
                }
            } 
        }
    }

    // combine setup and cleanup with config
    for variant, _ := result {
        // will return "cisco" for both "cisco" and "cisco.x3650"
        vendor := strings.Split(variant, ".")[0] 
        result[variant] = append(setupCmds[vendor], result[variant]...)
        result[variant] = append(result[variant], cleanupCmds[vendor]...)
    }

    return result

答案 1 :(得分:0)

您可以在构造cmdMap的循环之后将它们组合起来。

for vendor := range cmdMap {

    // get the base name of the vendor
    s := strings.SplitN(vender, ".", 2)

    // if vendor is a basic one, skip it.
    if len(s) == 1 {
        continue
    }

    // add basic cmd into the specified ones.
    base = s[0]
    cmdMap[vendor] = append(cmdMap[vendor], cmdMap[base]...)
}

请注意,cmdMap[base]...是对可变参数追加的使用。您可以在此处查看更多信息:https://golang.org/ref/spec#Passing_arguments_to_..._parameters