我有一个 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/