Traverse Json objects inside Arrays in Rego - json

I want to get the value of client_broker key and check if it is TLS or not ,if TLS the rego should pass else it will fail,but i am not able to traverse the nested json within the array element,tried using walk but not working,,,need help :(
Was trying using the below code but did not worked
deny{
r := tfplan.resource_changes[_]
r.type == "aws_msk_cluster"
name := r.change.after.encryption_info[_]
#val := walk(name,["encryption_in_transit"],"TLS")
print(name)
#print(val)
contains(name,"TLS")
}
Mock.json
"change": {
"actions": [
"create"
],
"before": null,
"after": {
"broker_node_group_info": [
{
"az_distribution": "DEFAULT",
"instance_type": "kafka.m5.large",
"storage_info": [
{
"ebs_storage_info": [
{
"provisioned_throughput": [],
"volume_size": 1000
}
]
}
]
}
],
"client_authentication": [],
"cluster_name": "example",
"configuration_info": [],
"encryption_info": [
{
"encryption_in_transit": [
{
"client_broker": "TLS",
"in_cluster": true
}
]
}
],
"enhanced_monitoring": "DEFAULT",
"kafka_version": "3.2.0",

Finally solved the issue by the below line of code
is_encryption_in_transit{
r := tfplan.resource_changes[_]
r.type == "aws_msk_cluster"
name := r.change.after.encryption_info[_]
[path,val] := walk(name.encryption_in_transit)
protocol = val["client_broker"]
cluster_encryption = val["in_cluster"]
protocol == "TLS"
cluster_encryption == true
}

Related

How to get the All index values in Groovy JSON xpath

Please find the attached Groovy code which I am using to get the particular filed from the response body.
Query 1 :
It is retrieving the results when the I am using the correct Index value like if the data.RenewalDetails[o], will give output as Value 1 and if the data.RenewalDetails[1], output as Value 2.
But in my real case, I will never know about number of blocks in the response, so I want to get all the values that are satisficing the condition, I tried data.RenewalDetails[*] but it is not working. Can you please help ?
Query 2:
Apart from the above condition, I want to add one more filter, where "FamilyCode": "PREMIUM" in the Itemdetails, Can you help on the same ?
def BoundId = new groovy.json.JsonSlurper().parseText('{"data":{"RenewalDetails":[{"ExpiryDetails":{"duration":"xxxxx","destination":"LHR","from":"AUH","value":2,"segments":[{"valudeid":"xxx-xx6262-xxxyyy-1111-11-11-1111"}]},"Itemdetails":[{"BoundId":"Value1","isexpired":true,"FamilyCode":"PREMIUM","availabilityDetails":[{"travelID":"AAA-AB1234-AAABBB-2022-11-10-1111","quota":"X","scale":"XXX","class":"X"}]}]},{"ExpiryDetails":{"duration":"xxxxx","destination":"LHR","from":"AUH","value":2,"segments":[{"valudeid":"xxx-xx6262-xxxyyy-1111-11-11-1111"}]},"Itemdetails":[{"BoundId":"Value2","isexpired":true,"FamilyCode":"PREMIUM","availabilityDetails":[{"travelID":"AAA-AB1234-AAABBB-2022-11-10-1111","quota":"X","scale":"XXX","class":"X"}]}]}]},"warnings":[{"code":"xxxx","detail":"xxxxxxxx","title":"xxxxxxxx"}]}')
.data.RenewalDetails[0].Itemdetails.find { itemDetail ->
itemDetail.availabilityDetails[0].travelID.length() == 33
}?.BoundId
println "Hello " + BoundId
Something like this:
def txt = '''\
{
"data": {
"RenewalDetails": [
{
"ExpiryDetails": {
"duration": "xxxxx",
"destination": "LHR",
"from": "AUH",
"value": 2,
"segments": [
{
"valudeid": "xxx-xx6262-xxxyyy-1111-11-11-1111"
}
]
},
"Itemdetails": [
{
"BoundId": "Value1",
"isexpired": true,
"FamilyCode": "PREMIUM",
"availabilityDetails": [
{
"travelID": "AAA-AB1234-AAABBB-2022-11-10-1111",
"quota": "X",
"scale": "XXX",
"class": "X"
}
]
}
]
},
{
"ExpiryDetails": {
"duration": "xxxxx",
"destination": "LHR",
"from": "AUH",
"value": 2,
"segments": [
{
"valudeid": "xxx-xx6262-xxxyyy-1111-11-11-1111"
}
]
},
"Itemdetails": [
{
"BoundId": "Value2",
"isexpired": true,
"FamilyCode": "PREMIUM",
"availabilityDetails": [
{
"travelID": "AAA-AB1234-AAABBB-2022-11-10-1111",
"quota": "X",
"scale": "XXX",
"class": "X"
}
]
}
]
}
]
},
"warnings": [
{
"code": "xxxx",
"detail": "xxxxxxxx",
"title": "xxxxxxxx"
}
]
}'''
def json = new groovy.json.JsonSlurper().parseText txt
List<String> BoundIds = json.data.RenewalDetails.Itemdetails*.find { itemDetail ->
itemDetail.availabilityDetails[0].travelID.size() == 33 && itemDetail.FamilyCode == 'PREMIUM'
}?.BoundId
assert BoundIds.toString() == '[Value1, Value2]'
Note, that you will get the BoundIds as a List
If you amend your code like this:
def json = new groovy.json.JsonSlurper().parse(prev.getResponseData()
you would be able to access the number of returned items as:
def size = json.data.RenewalDetails.size()
as RenewalDetails represents a List
Just add as many queries you want using Groovy's && operator:
find { itemDetail ->
itemDetail.availabilityDetails[0].travelID.length() == 33 &&
itemDetail.FamilyCode.equals('PREMIUM')
}
More information:
Apache Groovy - Parsing and producing JSON
Apache Groovy: What Is Groovy Used For?

Accessing list element in terraform output

I have following code in terraform state file( pasted just part of state file which I consider relevant for this question), which is result of running terraform code pasted below as well:
"mode": "managed",
"type": "azurerm_vpn_gateway",
"name": "azure_vpngw",
"provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"].azure_clusters",
"instances": [
{
"schema_version": 0,
"attributes": {
"bgp_settings": [
{
"asn": 65515,
"bgp_peering_address": "",
"instance_0_bgp_peering_address": [
{
"custom_ips": [
"169.254.21.1"
],
"default_ips": [
"10.255.176.12"
],
"ip_configuration_id": "Instance0",
"tunnel_ips": [
"10.255.176.4",
"20.184.79.231"
]
Relevant terraform code:
resource "azurerm_vpn_gateway" "azure_vpngw" {
provider = azurerm.azure_clusters
name = "azure_vpngw"
location = azurerm_resource_group.azure_networking.location
resource_group_name = azurerm_resource_group.azure_networking.name
virtual_hub_id = azurerm_virtual_hub.azure_hub.id
bgp_settings {
asn = 65515
peer_weight = 50
instance_0_bgp_peering_address {
custom_ips = ["169.254.21.1"]
}
instance_1_bgp_peering_address {
custom_ips = ["169.254.22.1"]
}
}
}
And I need to accesss last element in tunnel_ips list, so 20.184.79.231.
I have been trying diff things like
azurerm_vpn_gateway.azure_vpngw.bgp_settings[0].instance_0_bgp_peering_address[0].tunnel_ips[0][1]
but it did not work. So, issue is in last part - tunnel_ips[0][1] I believe. If anybody has idea, it would be very wellcome.
Since your code is not a valid TF code, I modified it to actually be such, and this is how you can access your ip:
locals {
t = <<EOL
{
"mode": "managed",
"type": "azurerm_vpn_gateway",
"name": "azure_vpngw",
"provider": "provider[azure_clusters",
"instances": [{
"schema_version": 0,
"attributes": {
"bgp_settings": [{
"asn": 65515,
"bgp_peering_address": "",
"instance_0_bgp_peering_address": [{
"custom_ips": [
"169.254.21.1"
],
"default_ips": [
"10.255.176.12"
],
"ip_configuration_id": "Instance0",
"tunnel_ips": [
"10.255.176.4",
"20.184.79.231"
]
}]
}]
}
}]
}
EOL
v = jsondecode(local.t)
}
output "test" {
value = local.v["instances"][0]["attributes"]["bgp_settings"][0]["instance_0_bgp_peering_address"][0]["tunnel_ips"][1]
}
gives:
test = "20.184.79.231"
I figure it out. So, to fetch IP and use it in another resource definition I did:
ip_address = sort(azurerm_vpn_gateway.azure_vpngw.bgp_settings[0].instance_0_bgp_peering_address[0].tunnel_ips)[1]

Protobuf custom options not showing in JSON made by protojson library

I'm trying to extract Protobuf custom options from a FileDescriptorSet generated by the protoc compiler. I'm unable to do so using protoreflect. So, I tried to do so using the protojson library.
PS : Importing the Go-generated code is not an option for my use case.
Here's the Protobuf Message I'm testing with :
syntax = "proto3";
option go_package = "./protoze";
import "google/protobuf/descriptor.proto";
extend google.protobuf.FieldOptions {
string Meta = 50000;
}
extend google.protobuf.FileOptions {
string Food = 50001;
}
option (Food) = "cheese";
message X {
int64 num = 1;
}
message P {
string Fname = 1 [json_name = "FNAME"];
string Lname = 2 [json_name = "0123", (Meta) = "Yo"];
string Designation = 3;
repeated string Email = 4;
string UserID = 5;
string EmpID = 6;
repeated X z = 7;
}
// protoc --go_out=. filename.proto
Here's how far I got :
package main
import (
"fmt"
"io/ioutil"
"os/exec"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/descriptorpb"
)
func main() {
exec.Command("protoc", "-oBinaryFile", "1.proto").Run()
Fset := descriptorpb.FileDescriptorSet{}
byts, _ := ioutil.ReadFile("File")
proto.Unmarshal(byts, &Fset)
byts, _ = protojson.Marshal(Fset.File[0])
fmt.Println(string(byts))
}
And here's the output JSON
{
"name": "1.proto",
"dependency": [
"google/protobuf/descriptor.proto"
],
"messageType": [
{
"name": "X",
"field": [
{
"name": "num",
"number": 1,
"label": "LABEL_OPTIONAL",
"type": "TYPE_INT64",
"jsonName": "num"
}
]
},
{
"name": "P",
"field": [
{
"name": "Fname",
"number": 1,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"jsonName": "FNAME"
},
{
"name": "Lname",
"number": 2,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"jsonName": "0123",
"options": {}
},
{
"name": "Designation",
"number": 3,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"jsonName": "Designation"
},
{
"name": "Email",
"number": 4,
"label": "LABEL_REPEATED",
"type": "TYPE_STRING",
"jsonName": "Email"
},
{
"name": "UserID",
"number": 5,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"jsonName": "UserID"
},
{
"name": "EmpID",
"number": 6,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"jsonName": "EmpID"
},
{
"name": "z",
"number": 7,
"label": "LABEL_REPEATED",
"type": "TYPE_MESSAGE",
"typeName": ".X",
"jsonName": "z"
}
]
}
],
"extension": [
{
"name": "Meta",
"number": 50000,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"extendee": ".google.protobuf.FieldOptions",
"jsonName": "Meta"
},
{
"name": "Food",
"number": 50001,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"extendee": ".google.protobuf.FileOptions",
"jsonName": "Food"
}
],
"options": {
"goPackage": "./protoze"
},
"syntax": "proto3"
}
So, data about my custom options showed up in the extensions. But what I really wanted was the value of those Custom Options in the "options" as well. (Which in my case was (Food) = "Cheese" and I want Cheese)
Can someone tell me how I can extract my custom options from the FileDescriptorSet using Protoreflect or by using Protojson.
I tried a lot to try and extract it using Protoreflect but failed !
Although not specifically an answer to how to get the custom options in a generated JSON, I believe I have an answer to what sounds like your underlying question: how to access the custom options without loading the generated Go code. This is thanks to dsnet's answer to my question on the golang issues board. Needless to say all the credit for this tricky solution goes to him. The punchline is to Marshal and then Unmarshal the options using a runtime-populated protoregistry.Types that actually knows about the custom options.
I made a complete demonstration of this approach working in this repo, and the key section (all the guts of which come from dsnet's example) is here:
func main() {
protogen.Options{
}.Run(func(gen *protogen.Plugin) error {
gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
// The type information for all extensions is in the source files,
// so we need to extract them into a dynamically created protoregistry.Types.
extTypes := new(protoregistry.Types)
for _, file := range gen.Files {
if err := registerAllExtensions(extTypes, file.Desc); err != nil {
panic(err)
}
}
// run through the files again, extracting and printing the Message options
for _, sourceFile := range gen.Files {
if !sourceFile.Generate {
continue
}
// setup output file
outputfile := gen.NewGeneratedFile("./out.txt", sourceFile.GoImportPath)
for _, message := range sourceFile.Messages {
outputfile.P(fmt.Sprintf("\nMessage %s:", message.Desc.Name()))
// The MessageOptions as provided by protoc does not know about
// dynamically created extensions, so they are left as unknown fields.
// We round-trip marshal and unmarshal the options with
// a dynamically created resolver that does know about extensions at runtime.
options := message.Desc.Options().(*descriptorpb.MessageOptions)
b, err := proto.Marshal(options)
if err != nil {
panic(err)
}
options.Reset()
err = proto.UnmarshalOptions{Resolver: extTypes}.Unmarshal(b, options)
if err != nil {
panic(err)
}
// Use protobuf reflection to iterate over all the extension fields,
// looking for the ones that we are interested in.
options.ProtoReflect().Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
if !fd.IsExtension() {
return true
}
outputfile.P(fmt.Sprintf("Value of option %s is %s",fd.Name(), v.String()))
// Make use of fd and v based on their reflective properties.
return true
})
}
}
return nil
})
}
// Recursively register all extensions into the provided protoregistry.Types,
// starting with the protoreflect.FileDescriptor and recursing into its MessageDescriptors,
// their nested MessageDescriptors, and so on.
//
// This leverages the fact that both protoreflect.FileDescriptor and protoreflect.MessageDescriptor
// have identical Messages() and Extensions() functions in order to recurse through a single function
func registerAllExtensions(extTypes *protoregistry.Types, descs interface {
Messages() protoreflect.MessageDescriptors
Extensions() protoreflect.ExtensionDescriptors
}) error {
mds := descs.Messages()
for i := 0; i < mds.Len(); i++ {
registerAllExtensions(extTypes, mds.Get(i))
}
xds := descs.Extensions()
for i := 0; i < xds.Len(); i++ {
if err := extTypes.RegisterExtension(dynamicpb.NewExtensionType(xds.Get(i))); err != nil {
return err
}
}
return nil
}

hcl, json, go : how can I iterate JSON?

Problem: I’m trying to iterate through JSON-content and present the result like key,value pairs.
I've written some code that read hcl-files, these are then decoded with hcldec.Decode, and the result is then converted to JSON. These hcl-files define source and target for the application like this:
source.hcl:
source json "namefile" {
attr firstName {
type = "varchar"
expr = "$.firstName"
length = "30"
}
attr lastName {
type = "varchar"
expr = "$.lastName"
length = "40"
}
attr gender {
type = "varchar"
expr = "$.gender"
length = "10"
}
attr age {
type = "varchar"
expr = "$.age"
length = "2"
}
}
target.hcl
target table {
cols firstName {
name=source.json.namefile.attr.firstName.expr
type=source.json.namefile.attr.firstName.type
length=source.json.namefile.attr.firstName.length
}
cols lastName {
name=source.json.namefile.attr.lastName.expr
type=source.json.namefile.attr.lastName.type
length=source.json.namefile.attr.lastName.length
}
}
The decoding is done like this:
tspec := hcldec.ObjectSpec{
"target": &hcldec.BlockMapSpec{
TypeName: "target",
LabelNames: []string{"table"},
Nested: hcldec.ObjectSpec{
"cols": &hcldec.BlockMapSpec{
TypeName: "cols",
LabelNames: []string{"name"},
Nested: &hcldec.ObjectSpec{
"name": &hcldec.AttrSpec{
Name: "name",
Type: cty.String, //cty.List(cty.String),
Required: false,
},
"type": &hcldec.AttrSpec{
Name: "type",
Type: cty.String, //cty.List(cty.String),
Required: false,
},
"length": &hcldec.AttrSpec{
Name: "length",
Type: cty.String, //cty.List(cty.String),
Required: false,
},
},
},
},
},
}
targ, _ := hcldec.Decode(body, tspec, &hcl.EvalContext{
Variables: map[string]cty.Value{
"source": val.GetAttr("source"),
},
Functions: nil,
})
j := decodeCtyToJson(targ, true)
log.Debugf("targ -j (spec): %s", string(j)) // debug info
Where the decodeCtyToJson return []byte like this:
func decodeCtyToJson(value cty.Value, pretty bool) []byte {
jsonified, err := ctyjson.Marshal(value, cty.DynamicPseudoType)
if err != nil {
log.Debugf("Error: #v", err)
return nil
}
if pretty {
return jsonPretty.Pretty(jsonified)
}
return jsonified
}
Now, when I'm trying to testprint the JSON-content I'm not getting what I'm looking for:
var result map[string]interface{}
json.Unmarshal(j, &result)
log.Debugf("result: %# v", result)
tgtfil := result["value"].(map[string]interface{})
log.Debugf("tgtfil: %v", tgtfil)
log.Debugf("len(tgtfil): %# v", len(tgtfil))
for key, value := range tgtfil {
log.Debugf("key: %# v", key)
log.Debugf("value: %# v", value)
}
I am trying to get key, value pairs. But I'm getting this (first the whole JSON pretty print as wanted, then I am trying to loop through the JSON):
DEBU[0000] targ -j (spec): {
"value": {
"target": {
"table": {
"cols": {
"firstName": {
"length": "30",
"name": "$.firstName",
"type": "varchar"
},
"lastName": {
"length": "40",
"name": "$.lastName",
"type": "varchar"
}
}
}
}
},
"type": [
"object",
{
"target": [
"map",
[
"object",
{
"cols": [
"map",
[
"object",
{
"length": "string",
"name": "string",
"type": "string"
}
]
]
}
]
]
}
]
}
DEBU[0000] result: map[string]interface {}{"type":[]interface {}{"object", map[string]interface {}{"target":[]interface {}{"map", []interface {}{"object", map[string]interface {}{"cols":[]interface {}{"map", []interface {}{"object", map[string]interface {}{"length":"string", "name":"string", "type":"string"}}}}}}}}, "value":map[string]interface {}{"target":map[string]interface {}{"table":map[string]interface {}{"cols":map[string]interface {}{"firstName":map[string]interface {}{"length":"30", "name":"$.firstName", "type":"varchar"}, "lastName":map[string]interface {}{"length":"40", "name":"$.lastName", "type":"varchar"}}}}}}
DEBU[0000] tgtfil: map[target:map[table:map[cols:map[firstName:map[length:30 name:$.firstName type:varchar] lastName:map[length:40 name:$.lastName type:varchar]]]]]
DEBU[0000] len(tgtfil): 1
DEBU[0000] key: "target"
DEBU[0000] value: map[string]interface {}{"table":map[string]interface {}{"cols":map[string]interface {}{"firstName":map[string]interface {}{"length":"30", "name":"$.firstName", "type":"varchar"}, "lastName":map[string]interface {}{"length":"40", "name":"$.lastName", "type":"varchar"}}}}
Process finished with exit code 0
My aim here is to eventually be able to iterate through alle the attributes defined in the target.hcl (length, name and type for each cols in this case). Then generate DDL-code from this information and finally implement the DDL in e.g. Presto.
But as of now I’m not able to isolate this information.
Any pointers on how to do this is appreciated.
Thanks,
/b
The solution for me was to create a struct for the target and not use the target spec.

How to create custom json structure (ansible inventory)

I am obtaining data from the cloudstack API listVirtualMachines, and trying to create a web service that will provide a dynamic ansible inventory for certain hosts.
In my first tries, currently I am fetching all the data by doing a connection request and then within a loop parse all the output:
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)
}
My desired output needs this JSON format:
{
"_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"
]
}
}
My first try was to create a huge map:
inventory := map[string]map[string]map[string]string{}
But that was covering only the structure of the _meta key :
{
"_meta": {
"hostvars": {
"x.x.x.x": {
"key": "value"
},
"x.x.x.y": {
"key": "value"
}
}
}
}
Later I came with a struct:
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)
}
The problem with this approach is that the returned JSON is adding both keys _meta and hosts:
{
"_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"
]
}
...
}
}
I would like to just have the key _meta and at the same level (inline) have each key for every hostname, something like:
{
"_meta": {...},
"server1": {
"hosts": []
},
"group1": {
"hosts": []
},
"sandbox": {
"children": []
}
}
I have a guess this probably could be solved with a map[string]interface{} maybe that could allow a dynamic JSON object with dynamic key names/structure, don't know exactly, therefore, any help would be appreciated.
You best bet is probably to let Ansible implement json.Marshaler to "flatten" the JSON document on-the-fly.
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)
}
Try it on the playground: https://play.golang.org/p/OKQHAG3vqIG