dictionary - Golang 映射 YAML 对象

标签 dictionary go configuration

我有一个 yaml 配置文件,其中包含要发送到网络设备的配置命令集。在每个集合中,有特定于 vendor 的键,每个 vendor 键的值可以是配置命令字符串、配置命令字符串列表或将特定于 vendor 的模型字符串映射到配置命令字符串。下面是一个例子:

# 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"]

我想像这样将这些命令组合成一个 map :

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 文件并能够将特定命令添加到每个 vendor 和模型,但是添加适用于 vendor 特定模型的命令是我遇到的问题。这是我的程序的实际输出,后面是我的代码:

$ 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 值?

最佳答案

我认为 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

关于dictionary - Golang 映射 YAML 对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51270780/

相关文章:

go - 如何使用gin管理Golang中的certfile更新?

button - 如何在 Aptana 中设置默认运行配置?

configuration - jetty -设置系统属性

python - 如何在Python中执行以下有序子集元素测试?

dictionary - 传单中框内的多个标记选择

python - Python中有 'multimap'实现吗?

java - 在android中对 map 进行排序

go - 使用 cgo,为什么在 golang 输出时 C 输出的不是 'survive' 管道?

performance - 如何测量golang中函数的执行时间,不包括等待时间

mysql - CakePHP:对 MySQL 用户和权限使用什么?