If I understand your question correctly, you are trying to convert Vault's path's into a map[string]interface{}
? That's not going to be very helpful because you're going to have to perform a type assertion on each read. Be warned this is a very expensive N^2 operation against Vault and the algorithm you've proposed could lose data because it's valid to share the same name for a folder and a leaf. Nonetheless, here you go. I think you'll find it's a lot more complicated than you expect:
package main
import (
"encoding/json"
"fmt"
"strings"
vault "github.com/hashicorp/vault/api"
)
func main() {
// Create a client object.
client, err := vault.NewClient(vault.DefaultConfig())
if err != nil {
panic(err)
}
// Convert everything at the path to a map. This will only work with KV v1.
m, err := SecretsToMap(client, "secret/")
if err != nil {
panic(err)
}
// Dump the result as JSON for easier viewing.
j, err := json.MarshalIndent(m, "", " ")
if err != nil {
panic(err)
}
fmt.Printf("%s", j)
}
// SecretsToMap recursively reads all paths in the root and converts them to a
// map by each path segment.
func SecretsToMap(client *vault.Client, root string) (map[string]interface{}, error) {
var m = make(map[string]interface{})
if err := secretsToMap(client, m, root); err != nil {
return nil, err
}
return m, nil
}
func secretsToMap(client *vault.Client, m map[string]interface{}, root string) error {
// List everything at this path
r, err := client.Logical().List(root)
if err != nil {
return fmt.Errorf("failed to list %s: %s", root, err)
}
// Do nothing if the leaf is empty - this might be undesireable depending on
// your use case (you may want the empty leaf here with {}). Modify as
// appropriate.
if r == nil || len(r.Data) == 0 {
return nil
}
// Type conversions to get the response as []string.
keysRaw, ok := r.Data["keys"]
if !ok {
return nil
}
keys, ok := keysRaw.([]interface{})
if !ok {
return fmt.Errorf("%q is not []interface{}", keysRaw)
}
list := make([]string, len(keys))
for i, v := range keys {
str, ok := v.(string)
if !ok {
return fmt.Errorf("%q is not string", v)
}
list[i] = str
}
// Iterate over each response
for _, v := range list {
// pth is the full path (root + child)
pth := root + v
// If this is a folder, recurse.
if strings.HasSuffix(v, "/") {
if err := secretsToMap(client, m, pth); err != nil {
return err
}
} else {
// Not a folde,r read the secret, handing errors and nil/empty data
secret, err := client.Logical().Read(pth)
if err != nil {
return fmt.Errorf("failed to read secret %s: %s", pth, err)
}
// This might not be what you want - modify as appropriate.
if secret == nil || secret.Data == nil {
return nil
}
// Iterate over the folder parts and make a map entry if it does not
// exist. This will panic and fail if you have leafs and folders with the
// same name, but I'm just following your example.
ptr := m
for _, v := range strings.Split(root, "/") {
p := strings.Trim(v, "/")
if p == "" {
continue
}
// If there's no value at ptr (which is a moving target within the map),
// create a container for data.
if _, ok := ptr[p]; !ok {
ptr[p] = map[string]interface{}{}
}
// Advance the map pointer deeper into the map.
ptr = ptr[p].(map[string]interface{})
}
// We've reached the leaf, set the data.
ptr[v] = secret.Data
}
}
return nil
}