Skip to content

Commit

Permalink
feat: Support marshal and unmarshal (#10)
Browse files Browse the repository at this point in the history
* add Makefile
* expose all private fields
* support marshaling and unmarshaling
* upgrade go to 1.20
  • Loading branch information
jiping-s committed Jul 17, 2023
1 parent deb9d0f commit 0a9c586
Show file tree
Hide file tree
Showing 11 changed files with 331 additions and 60 deletions.
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
SOURCES ?= $(shell find . -name '*.go')
SOURCES_NONTEST ?= $(shell find . -name '*.go' -not -name '*_test.go')

.PHONY: test
test:
go test -timeout $${TEST_TIMEOUT:-10s} -v ./...

# test-all ignores testcache (go clean testcache)
.PHONY: test-all
test-all:
go test -timeout $${TEST_TIMEOUT:-10s} -v -count=1 ./...

.PHONY: upgrade
upgrade:
rm -f go.sum
go get -u -d ./...; go mod tidy
24 changes: 12 additions & 12 deletions aini.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"bufio"
"bytes"
"io"
"io/ioutil"
"os"
"path"
"sort"
"strings"
Expand All @@ -25,15 +25,15 @@ type Group struct {
Children map[string]*Group
Parents map[string]*Group

directParents map[string]*Group
DirectParents map[string]*Group
// Vars set in inventory
inventoryVars map[string]string
InventoryVars map[string]string
// Vars set in group_vars
fileVars map[string]string
FileVars map[string]string
// Projection of all parent inventory variables
allInventoryVars map[string]string
AllInventoryVars map[string]string
// Projection of all parent group_vars variables
allFileVars map[string]string
AllFileVars map[string]string
}

// Host represents ansible host
Expand All @@ -43,16 +43,16 @@ type Host struct {
Vars map[string]string
Groups map[string]*Group

directGroups map[string]*Group
DirectGroups map[string]*Group
// Vars set in inventory
inventoryVars map[string]string
InventoryVars map[string]string
// Vars set in host_vars
fileVars map[string]string
FileVars map[string]string
}

// ParseFile parses Inventory represented as a file
func ParseFile(f string) (*InventoryData, error) {
bs, err := ioutil.ReadFile(f)
bs, err := os.ReadFile(f)
if err != nil {
return &InventoryData{}, err
}
Expand Down Expand Up @@ -143,7 +143,7 @@ func hostMapToLower(hosts map[string]*Host, keysOnly bool) map[string]*Host {
func (inventory *InventoryData) GroupsToLower() {
inventory.Groups = groupMapToLower(inventory.Groups, false)
for _, host := range inventory.Hosts {
host.directGroups = groupMapToLower(host.directGroups, true)
host.DirectGroups = groupMapToLower(host.DirectGroups, true)
host.Groups = groupMapToLower(host.Groups, true)
}
}
Expand All @@ -162,7 +162,7 @@ func groupMapToLower(groups map[string]*Group, keysOnly bool) map[string]*Group
groupname = strings.ToLower(groupname)
if !keysOnly {
group.Name = groupname
group.directParents = groupMapToLower(group.directParents, true)
group.DirectParents = groupMapToLower(group.DirectParents, true)
group.Parents = groupMapToLower(group.Parents, true)
group.Children = groupMapToLower(group.Children, true)
}
Expand Down
13 changes: 10 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
module github.com/relex/aini

go 1.13
go 1.20

require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/stretchr/testify v1.7.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
github.com/samber/lo v1.38.1
github.com/stretchr/testify v1.7.0
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
)
10 changes: 7 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
28 changes: 14 additions & 14 deletions inventory.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ func (inventory *InventoryData) Reconcile() {

allGroup := inventory.getOrCreateGroup("all")
ungroupedGroup := inventory.getOrCreateGroup("ungrouped")
ungroupedGroup.directParents[allGroup.Name] = allGroup
ungroupedGroup.DirectParents[allGroup.Name] = allGroup

// First, ensure that inventory.Groups contains all the groups
for _, host := range inventory.Hosts {
for _, group := range host.directGroups {
for _, group := range host.DirectGroups {
inventory.Groups[group.Name] = group
for _, ancestor := range group.ListParentGroupsOrdered() {
inventory.Groups[ancestor.Name] = ancestor
Expand All @@ -37,7 +37,7 @@ func (inventory *InventoryData) Reconcile() {

// Calculate intergroup relationships
for _, group := range inventory.Groups {
group.directParents[allGroup.Name] = allGroup
group.DirectParents[allGroup.Name] = allGroup
for _, ancestor := range group.ListParentGroupsOrdered() {
group.Parents[ancestor.Name] = ancestor
ancestor.Children[group.Name] = group
Expand All @@ -47,7 +47,7 @@ func (inventory *InventoryData) Reconcile() {
// Now set hosts for groups and groups for hosts
for _, host := range inventory.Hosts {
host.Groups[allGroup.Name] = allGroup
for _, group := range host.directGroups {
for _, group := range host.DirectGroups {
group.Hosts[host.Name] = host
host.Groups[group.Name] = group
for _, parent := range group.Parents {
Expand All @@ -64,7 +64,7 @@ func (inventory *InventoryData) Reconcile() {
func (host *Host) clearData() {
host.Groups = make(map[string]*Group)
host.Vars = make(map[string]string)
for _, group := range host.directGroups {
for _, group := range host.DirectGroups {
group.clearData(make(map[string]struct{}, len(host.Groups)))
}
}
Expand All @@ -77,10 +77,10 @@ func (group *Group) clearData(visited map[string]struct{}) {
group.Parents = make(map[string]*Group)
group.Children = make(map[string]*Group)
group.Vars = make(map[string]string)
group.allInventoryVars = nil
group.allFileVars = nil
group.AllInventoryVars = nil
group.AllFileVars = nil
visited[group.Name] = struct{}{}
for _, parent := range group.directParents {
for _, parent := range group.DirectParents {
parent.clearData(visited)
}
}
Expand All @@ -97,9 +97,9 @@ func (inventory *InventoryData) getOrCreateGroup(groupName string) *Group {
Children: make(map[string]*Group),
Parents: make(map[string]*Group),

directParents: make(map[string]*Group),
inventoryVars: make(map[string]string),
fileVars: make(map[string]string),
DirectParents: make(map[string]*Group),
InventoryVars: make(map[string]string),
FileVars: make(map[string]string),
}
inventory.Groups[groupName] = g
return g
Expand All @@ -116,9 +116,9 @@ func (inventory *InventoryData) getOrCreateHost(hostName string) *Host {
Groups: make(map[string]*Group),
Vars: make(map[string]string),

directGroups: make(map[string]*Group),
inventoryVars: make(map[string]string),
fileVars: make(map[string]string),
DirectGroups: make(map[string]*Group),
InventoryVars: make(map[string]string),
FileVars: make(map[string]string),
}
inventory.Hosts[hostName] = h
return h
Expand Down
74 changes: 74 additions & 0 deletions marshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package aini

import (
"encoding/json"

"github.com/samber/lo"
"golang.org/x/exp/maps"
)

type alwaysNil interface{} // to hold place for Group and Host references; must be nil in serialized form

func (group *Group) MarshalJSON() ([]byte, error) {
type groupWithoutCustomMarshal Group

return json.Marshal(&struct {
groupWithoutCustomMarshal
Hosts map[string]alwaysNil
Children map[string]alwaysNil
Parents map[string]alwaysNil
DirectParents map[string]alwaysNil
}{
groupWithoutCustomMarshal: groupWithoutCustomMarshal(*group),
Hosts: makeNilValueMap(group.Hosts),
Children: makeNilValueMap(group.Children),
Parents: makeNilValueMap(group.Parents),
DirectParents: makeNilValueMap(group.DirectParents),
})
}

func (host *Host) MarshalJSON() ([]byte, error) {
type hostWithoutCustomMarshal Host

return json.Marshal(&struct {
hostWithoutCustomMarshal
Groups map[string]alwaysNil
DirectGroups map[string]alwaysNil
}{
hostWithoutCustomMarshal: hostWithoutCustomMarshal(*host),
Groups: makeNilValueMap(host.Groups),
DirectGroups: makeNilValueMap(host.DirectGroups),
})
}

func makeNilValueMap[K comparable, V any](m map[K]*V) map[K]alwaysNil {
return lo.MapValues(m, func(_ *V, _ K) alwaysNil { return nil })
}

func (inventory *InventoryData) UnmarshalJSON(data []byte) error {
type inventoryWithoutCustomUnmarshal InventoryData
var rawInventory inventoryWithoutCustomUnmarshal
if err := json.Unmarshal(data, &rawInventory); err != nil {
return err
}
// rawInventory's Groups and Hosts should now contain all properties,
// except child group maps and host maps are filled with original keys and null values

// reassign child groups and hosts to reference rawInventory.Hosts and .Groups

for _, group := range rawInventory.Groups {
group.Hosts = lo.PickByKeys(rawInventory.Hosts, maps.Keys(group.Hosts))
group.Children = lo.PickByKeys(rawInventory.Groups, maps.Keys(group.Children))
group.Parents = lo.PickByKeys(rawInventory.Groups, maps.Keys(group.Parents))
group.DirectParents = lo.PickByKeys(rawInventory.Groups, maps.Keys(group.DirectParents))
}

for _, host := range rawInventory.Hosts {
host.Groups = lo.PickByKeys(rawInventory.Groups, maps.Keys(host.Groups))
host.DirectGroups = lo.PickByKeys(rawInventory.Groups, maps.Keys(host.DirectGroups))
}

inventory.Groups = rawInventory.Groups
inventory.Hosts = rawInventory.Hosts
return nil
}
57 changes: 57 additions & 0 deletions marshal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package aini

import (
_ "embed"
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
)

const minMarshalInventory = `[Animals]
ET
[Animals:children]
Cats
[Cats]
Lion
`

//go:embed marshal_test_inventory.json
var minMarshalJSON string

func TestMarshalJSON(t *testing.T) {
v, err := ParseString(minMarshalInventory)
assert.Nil(t, err)

j, err := json.MarshalIndent(v, "", " ")
assert.Nil(t, err)
assert.Equal(t, minMarshalJSON, string(j))

t.Run("unmarshal", func(t *testing.T) {
var v2 InventoryData
assert.Nil(t, json.Unmarshal(j, &v2))
assert.Equal(t, v.Hosts["Lion"], v2.Hosts["Lion"])
assert.Equal(t, v.Groups["Cats"], v2.Groups["Cats"])
})
}

func TestMarshalWithVars(t *testing.T) {
v, err := ParseFile("test_data/inventory")
assert.Nil(t, err)

v.HostsToLower()
v.GroupsToLower()
v.AddVarsLowerCased("test_data")

j, err := json.MarshalIndent(v, "", " ")
assert.Nil(t, err)

t.Run("unmarshal", func(t *testing.T) {
var v2 InventoryData
assert.Nil(t, json.Unmarshal(j, &v2))
assert.Equal(t, v.Hosts["host1"], v2.Hosts["host1"])
assert.Equal(t, v.Groups["tomcat"], v2.Groups["tomcat"])
})
}
Loading

0 comments on commit 0a9c586

Please sign in to comment.