title: Golang Map YAML Objects
body: <p>I have a yaml configuration file that contains sets of configuration commands to send to network devices. Inside of each set, there are vendor-specific keys and the values for each vendor key can be either a configuration command string, a list of configuration command strings, or a list of key-value pairs mapping a vendor-specific model string to a configuration command string. Below is an example:</p>
  
  <pre><code># 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"]
  </code></pre>
  
  <p>I want to combine these commands into a map like so:</p>
  
  <pre><code>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",
      }
  }
  </code></pre>
  
  <p>I am using <a href="https://github.com/spf13/viper" rel="nofollow noreferrer">spf13/viper</a> to handle parsing the yaml file and have been able to add the specific commands to each vendor and model, but adding the commands that apply to both vendor <em>and</em> specific model is where I am stuck. This is the actual output of my program followed by my code:</p>
  
  <pre><code>$ 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
  </code></pre>
  
  <p>My code:</p>
  
  <pre><code>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
  }
  </code></pre>
  
  <p><strong>How can I combine the "universal" and model-specific commands to get the expected <code>cmdMap</code> value from above?</strong></p>