如何创建自定义json结构(ansible inventory)

时间:2018-04-11 19:26:17

标签: json go

我从cloudstack API listVirtualMachines获取数据,并尝试创建一个为某些主机提供dynamic ansible inventory的网络服务。

在我的第一次尝试中,目前我通过执行连接请求获取所有数据,然后在循环中解析所有输出:

vms, _ := cs.Request(&cs.ListVirtualMachines{})

// use for the metadata _meta['hostvars']['IP']['key'] = val
hostvars := make(map[string]map[string]string)

// used per each host ['name']['hosts'] = [list]
hosts := make(map[string]map[string][]string)

for _, vm := range vms(*cs.ListVirtualMachinesResponse).VirtualMachine {
  ip := vm.Nic[0].IPAddress.String()
  if ip == "" {
      continue
  }
  hostvars[ip] = map[string]string{
      vm.DisplayName:            vm.DisplayName,
      "ansible_ssh_common_args": "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null",
  }
  if hosts[vm.DisplayName] == nil {
      hosts[vm.DisplayName] = map[string][]string{}
  }
  hosts[vm.DisplayName]["hosts"] = append(hosts[vm.DisplayName]["hosts"], ip)
}

我想要的输出需要这种JSON格式:

{
    "_meta": {
        "hostvars": {
            "172.16.0.3": {
                "ansible_ssh_common_args": "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null",
                "displayname": "server 1"
            },
            "172.16.0.4": {
                "ansible_ssh_common_args": "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null",
                "displayname": "server 2"
            },
            "172.16.0.5": {
                "ansible_ssh_common_args": "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null",
                "displayname": "server 3"
            }
        }
    },
    "group1": {
        "hosts": [
            "172.16.0.3",
            "172.16.0.4",
            "172.16.0.5"
        ]
    },
    "sandbox": {
        "children": [
            "server1",
            "server2",
            "server3"
        ]
    },
    "server1": {
        "hosts": [
            "172.16.0.3"
        ]
    },
    "server2": {
        "hosts": [
            "172.16.0.4"
        ]
    },
    "server3": {
        "hosts": [
            "172.16.0.5"
        ]
    }
}

我的第一次尝试是创建一个巨大的地图:

inventory := map[string]map[string]map[string]string{}

但这只涉及_meta键的结构:

{
    "_meta": {
        "hostvars": {
            "x.x.x.x": {
                "key": "value"
            },
            "x.x.x.y": {
                "key": "value"
            }
        }
    }
}

后来我带来了一个结构:

type Ansible struct {
        Metadata Hostvars `json:"_meta"`
        Hosts map[string]map[string][]string `json:",inline"` // want to remove the Hosts key
}

type Hostvars struct {
        Hosts map[string]map[string]string `json:"hostvars"`
}

inventory := &Ansible{
                Hostvars{hostvars},
                hosts,
}


if err := json.NewEncoder(os.Stdout).Encode(inventory); err != nil {
        log.Println(err)
}

此方法的问题在于返回的JSON正在添加键_metahosts

{
    "_meta": {
        "hostvars": {
            "x.x.x.x": {
                "key": "value"
            },
            "x.x.x.y": {
                "key": "value"
            }
        }
    },
    "hosts": {    <--- want to remove this 
        "server1": {
            "hosts": [
                "172.16.0.3"
            ]
        }
        ...
    }
}

我希望只有密钥_meta并且在同一级别(内联)上为每个主机名分配每个密钥,例如:

{
    "_meta": {...},
    "server1": {
        "hosts": []
    },
    "group1": {
        "hosts": []
    },
    "sandbox": {
        "children": []
    }
}

我猜这可能可以通过map[string]interface{}来解决,这可能允许动态JSON对象具有动态密钥名称/结构,但是不确切知道,因此,任何帮助都将受到赞赏。

1 个答案:

答案 0 :(得分:0)

你最好的选择可能是让Ansible实施json.Marshaler来“平铺”JSON文档。

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    a := &Ansible{
            Metadata: Hostvars{
                    Hosts: map[string]map[string]string{
                            "172.16.0.3": map[string]string{
                                    "displayname": "server 1",
                            },
                    },
            },
            Hosts: map[string]Group{
                    "group1":  Group{Hosts: []string{"172.16.0.3", "172.16.0.4", "172.16.0.5"}},
                    "sandbox": Group{Children: []string{"server1", "server2", "server3"}},
                    "server1": Group{Hosts: []string{"172.16.0.3"}},
            },
    }

    b, err := json.MarshalIndent(a, "", "  ")
    fmt.Println(string(b), err)
}

type Ansible struct {
    Metadata Hostvars
    Hosts    map[string]Group
}

type Hostvars struct {
    Hosts map[string]map[string]string `json:"hostvars"`
}

// I don't *think* Ansible supports anything else in host groups, so it makes
// sense to define it as a struct.
type Group struct {
    Hosts    []string `json:"hosts,omitempty"`
    Children []string `json:"children,omitempty"`
}

// MarshalJSON implements json.Marshaler
func (a *Ansible) MarshalJSON() ([]byte, error) {
    // Define a map that we will encode at the end of the function.
    doc := make(map[string]interface{})

    // Add all the groups.
    for k, v := range a.Hosts {
            doc[k] = v
    }

    // Add the _meta key.
    doc["_meta"] = a.Metadata

    return json.Marshal(doc)
}

在操场上试试:https://play.golang.org/p/OKQHAG3vqIG