Skip to content

Commit e98b42d

Browse files
author
Nicolas Sterchele
authored
Merge pull request #4 from peopledoc/feature/query-inventory
Add `ansible list` command
2 parents c7fee20 + bffb278 commit e98b42d

File tree

17 files changed

+1386
-13
lines changed

17 files changed

+1386
-13
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
# Output of the go coverage tool, specifically when used with LiteIDE
1212
*.out
1313

14+
/pkg
1415
bin/
15-
pkg/
1616

1717
# Editor files
1818
*.swp

Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ GOGENERATE=$(GOCMD) generate
1414
GOTEST=$(GOCMD) test
1515
GOBUILDFLAGS= "-trimpath"
1616

17+
.PHONY: build
18+
build:
19+
$(GOBUILD) -o $(BINDIR)/$(BINNAME) -v $(GOBUILDFLAGS)
20+
1721
.PHONY: devel-deps
1822
devel-deps:
1923
@for tool in $(EXTERNAL_TOOLS) ; do \
2024
echo "Installing $$tool" ; \
2125
GO111MODULE=off go get $$tool; \
2226
done
2327

24-
.PHONY: build
25-
build:
26-
$(GOBUILD) -o $(BINDIR)/$(BINNAME) -v $(GOBUILDFLAGS)
27-
2828
.PHONY: test
2929
test:
3030
$(GOTEST) -v ./...

cmd/ansible.go

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package cmd
22

33
import (
4+
"bufio"
45
"fmt"
6+
"io"
57
"jarvis/internal/pkg/ansible"
68
"jarvis/internal/pkg/command"
79
"jarvis/internal/pkg/environment"
@@ -11,39 +13,62 @@ import (
1113
"github.com/spf13/viper"
1214
)
1315

16+
//Ansible flags
17+
var (
18+
HideDiff bool
19+
BecomeSudo bool
20+
)
21+
22+
//Playbook flags
1423
var (
15-
HideDiff bool
1624
CheckModeDeactivated bool
17-
CheckModeEnabled bool
18-
BecomeSudo bool
1925
JoinInventories bool
2026
)
2127

28+
//Run flags
29+
var (
30+
CheckModeEnabled bool
31+
ModuleName string
32+
ModuleArg string
33+
HostPattern string
34+
)
35+
36+
//Inventory flags
2237
var (
23-
ModuleName string
24-
ModuleArg string
25-
HostPattern string
38+
ListGroup bool
39+
HostGroupName string
40+
WithParent bool
2641
)
2742

2843
func init() {
2944
rootCmd.AddCommand(ansibleCmd)
30-
ansibleCmd.AddCommand(playCmd)
45+
//AnsibleCmd
3146
ansibleCmd.PersistentFlags().BoolVar(&HideDiff, "nodiff", false,
3247
"Hide diff")
3348
ansibleCmd.PersistentFlags().BoolVarP(&BecomeSudo, "become", "b", false,
3449
"Become sudo")
50+
51+
//PlaybookCmd
3552
playCmd.Flags().BoolVar(&CheckModeDeactivated, "nocheck", false,
3653
"Deactivate check mode")
3754
playCmd.Flags().BoolVar(&JoinInventories, "join-inventories", false,
3855
"Join platforms inventories")
56+
ansibleCmd.AddCommand(playCmd)
3957

40-
ansibleCmd.AddCommand(runCmd)
58+
//RunCmd
4159
runCmd.Flags().StringVarP(&ModuleName, "module", "m", "shell", "Ansible module name")
4260
runCmd.Flags().StringVarP(&ModuleArg, "args", "a", "", "Ansible module arg")
4361
runCmd.Flags().StringVarP(&HostPattern, "target", "t", "", "Ansible host-pattern")
4462
runCmd.Flags().BoolVar(&CheckModeEnabled, "check", false,
4563
"Enable check mode")
4664
runCmd.MarkFlagRequired("target")
65+
ansibleCmd.AddCommand(runCmd)
66+
67+
//InventoryCmd
68+
inventoryCmd.Flags().BoolVarP(&WithParent, "with-parent", "W", false, "Query with parent group")
69+
inventoryCmd.Flags().BoolVarP(&ListGroup, "group", "G", false, "List group name (mutually exclusive with --host)")
70+
inventoryCmd.Flags().StringVarP(&HostGroupName, "host", "H", "", "List host by group name (mutually exclusive with --group)")
71+
ansibleCmd.AddCommand(inventoryCmd)
4772
}
4873

4974
//usage: jarvis ansible
@@ -148,3 +173,85 @@ var playCmd = &cobra.Command{
148173
return nil
149174
},
150175
}
176+
177+
//usage: jarvis ansible list
178+
//returns:
179+
// --group(bool) >> list groups name
180+
// --hosts(string) >> hosts by group
181+
var inventoryCmd = &cobra.Command{
182+
Use: "list",
183+
Short: "Query inventory",
184+
PreRunE: func(cmd *cobra.Command, args []string) error {
185+
//checking exclusivity
186+
if ListGroup && HostGroupName != "" {
187+
return fmt.Errorf("--group and --hosts are mutually exclusive")
188+
}
189+
190+
return nil
191+
},
192+
RunE: func(cmd *cobra.Command, args []string) error {
193+
envsPath := viper.GetString("environments.path")
194+
195+
allInventories, err := environment.GetFullPathInventoriesFromEnvironments(envsPath, *environments)
196+
if err != nil {
197+
return err
198+
}
199+
200+
var invReaders []io.Reader
201+
for _, invs := range allInventories {
202+
for _, inv := range invs {
203+
if !fileExists(inv) {
204+
return fmt.Errorf("the %v file does not exist", inv)
205+
}
206+
f, err := os.Open(inv)
207+
if err != nil {
208+
return err
209+
}
210+
invReaders = append(invReaders, bufio.NewReader(f))
211+
212+
if isDebug {
213+
fmt.Println(inv)
214+
}
215+
}
216+
}
217+
//to concatenate all files to one reader
218+
r := io.MultiReader(invReaders...)
219+
manipulator := ansible.InitInventoryManipulator(r)
220+
221+
if ListGroup {
222+
groups, err := manipulator.GetGroupsName(WithParent)
223+
if err != nil {
224+
return err
225+
}
226+
//I know it is a bit odd but Jarvis is learning
227+
//be nice with him :)
228+
for _, g := range groups {
229+
fmt.Println(g)
230+
}
231+
232+
return nil
233+
}
234+
235+
if HostGroupName != "" {
236+
hosts, err := manipulator.GetHostsByGroupName(HostGroupName)
237+
if err != nil {
238+
return err
239+
}
240+
for _, h := range hosts {
241+
fmt.Println(h)
242+
}
243+
244+
return nil
245+
}
246+
247+
return nil
248+
},
249+
}
250+
251+
func fileExists(filename string) bool {
252+
info, err := os.Stat(filename)
253+
if os.IsNotExist(err) {
254+
return false
255+
}
256+
return !info.IsDir()
257+
}

cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func init() {
4444
StringVarP(&envName, "env", "e", "", "Environment name, syntax(type.env.platform)")
4545
rootCmd.PersistentFlags().
4646
BoolVar(&isDebug, "debug", false, "debug mode")
47+
rootCmd.MarkPersistentFlagRequired("env")
4748

4849
cobra.OnInitialize(initConfig)
4950
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.13
55
require (
66
github.com/golang/mock v1.3.1
77
github.com/pelletier/go-toml v1.6.0 // indirect
8+
github.com/relex/aini v0.0.0-00010101000000-000000000000
89
github.com/spf13/afero v1.2.2 // indirect
910
github.com/spf13/cast v1.3.1 // indirect
1011
github.com/spf13/cobra v0.0.5
@@ -16,3 +17,5 @@ require (
1617
gopkg.in/ini.v1 v1.51.1 // indirect
1718
gopkg.in/yaml.v2 v2.2.7
1819
)
20+
21+
replace github.com/relex/aini => ./internal/pkg/aini

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
3939
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
4040
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
4141
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
42+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
43+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
4244
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
4345
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
4446
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=

internal/pkg/aini/LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2020 RELEX Oy
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.

internal/pkg/aini/README.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# aini
2+
3+
Go library for Parsing Ansible inventory files.
4+
We are trying to follow the logic of Ansible parser as close as possible.
5+
6+
Documentation on ansible inventory files can be found here:
7+
https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html
8+
9+
## Supported features:
10+
- [X] Variables
11+
- [X] Host patterns
12+
- [X] Nested groups
13+
14+
## Public API
15+
```godoc
16+
package aini // import "github.com/relex/aini"
17+
18+
19+
TYPES
20+
21+
type Group struct {
22+
Name string
23+
Vars map[string]string
24+
Hosts map[string]*Host
25+
Children map[string]*Group
26+
Parents map[string]*Group
27+
}
28+
Group represents ansible group
29+
30+
func GroupMapListValues(mymap map[string]*Group) []*Group
31+
GroupMapListValues transforms map of Groups into Group list
32+
33+
type Host struct {
34+
Name string
35+
Port int
36+
Vars map[string]string
37+
Groups map[string]*Group
38+
}
39+
Host represents ansible host
40+
41+
func HostMapListValues(mymap map[string]*Host) []*Host
42+
HostMapListValues transforms map of Hosts into Host list
43+
44+
type InventoryData struct {
45+
Groups map[string]*Group
46+
Hosts map[string]*Host
47+
}
48+
InventoryData contains parsed inventory representation Note: Groups and
49+
Hosts fields contain all the groups and hosts, not only top-level
50+
51+
func Parse(r io.Reader) (*InventoryData, error)
52+
Parse using some Reader
53+
54+
func ParseFile(f string) (*InventoryData, error)
55+
ParseFile parses Inventory represented as a file
56+
57+
func ParseString(input string) (*InventoryData, error)
58+
ParseString parses Inventory represented as a string
59+
60+
func (inventory *InventoryData) GroupsToLower()
61+
GroupsToLower transforms all group names to lowercase
62+
63+
func (inventory *InventoryData) HostsToLower()
64+
HostsToLower transforms all host names to lowercase
65+
66+
func (inventory *InventoryData) Match(m string) []*Host
67+
Match looks for a hosts that match the pattern
68+
69+
func (inventory *InventoryData) Reconcile()
70+
Reconcile ensures inventory basic rules, run after updates
71+
72+
```
73+
74+
## Usage example
75+
```go
76+
import (
77+
"strings"
78+
79+
"github.com/relex/aini"
80+
)
81+
82+
func main() {
83+
// Load from string example
84+
inventoryReader := strings.NewReader(`
85+
host1:2221
86+
[web]
87+
host2 ansible_ssh_user=root
88+
`)
89+
var inventory InventoryData = aini.Parse(inventoryReader)
90+
91+
// Querying hosts
92+
_ = inventory.Hosts["host1"].Name == "host1" // true
93+
_ = inventory.Hosts["host1"].Port == 2221 // true
94+
_ = inventory.Hosts["host2"].Name == "host2"] // true
95+
_ = inventory.Hosts["host2"].Post == 22] // true
96+
97+
_ = len(inventory.Hosts["host1"].Groups) == 2 // all, ungrouped
98+
_ = len(inventory.Hosts["host2"].Groups) == 2 // all, web
99+
100+
_ = len(inventory.Match("host*")) == 2 // host1, host2
101+
102+
_ = // Querying groups
103+
_ = inventory.Groups["web"].Hosts[0].Name == "host2" // true
104+
_ = len(inventory.Groups["all"].Hosts) == 2 // true
105+
}
106+
```

0 commit comments

Comments
 (0)