aboutsummaryrefslogtreecommitdiff
path: root/clients
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2022-12-23 23:05:47 -0800
committerMike Crute <mike@crute.us>2022-12-23 23:05:47 -0800
commit85c67dd133605d2b1125a1b0e8c28f991223aa75 (patch)
tree89e04d1ee2603431d265335942d46c5108b06260 /clients
parent15ef537afcded7561f59d1890df14af7f8277752 (diff)
downloadgolib-85c67dd133605d2b1125a1b0e8c28f991223aa75.tar.bz2
golib-85c67dd133605d2b1125a1b0e8c28f991223aa75.tar.xz
golib-85c67dd133605d2b1125a1b0e8c28f991223aa75.zip
clients/netbox: add config file clientclients/netbox/v3.2.0
Diffstat (limited to 'clients')
-rw-r--r--clients/netbox/config_file_client.go151
-rw-r--r--clients/netbox/config_file_client_test.go165
-rw-r--r--clients/netbox/go.mod12
-rw-r--r--clients/netbox/go.sum21
4 files changed, 349 insertions, 0 deletions
diff --git a/clients/netbox/config_file_client.go b/clients/netbox/config_file_client.go
new file mode 100644
index 0000000..6023916
--- /dev/null
+++ b/clients/netbox/config_file_client.go
@@ -0,0 +1,151 @@
1package netbox
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io/fs"
8 "net"
9 "path/filepath"
10
11 "github.com/mitchellh/mapstructure"
12 "gopkg.in/yaml.v2"
13)
14
15type ConfigFileNetboxClient struct {
16 cfg map[string]any
17}
18
19var _ NetboxClient = (*ConfigFileNetboxClient)(nil)
20
21// NewConfigFileClient creates a new ConfigFileNetboxClient by loading a
22// named config file from a filesystem and unmarshalling it. The config
23// file can be in JSON or YAML format, determined by a .json, .yaml, or
24// .yml extension. The configuration must be nested within a key in that
25// file to support sharing the file with other subsystems.
26//
27// See method docs for the extected format of this file.
28func NewConfigFileClient(filesystem fs.FS, name, key string) (NetboxClient, error) {
29 fp, err := filesystem.Open(name)
30 if err != nil {
31 return nil, err
32 }
33 defer fp.Close()
34
35 fc := map[string]any{}
36
37 switch e := filepath.Ext(name); e {
38 case ".yaml", ".yml":
39 if err := yaml.NewDecoder(fp).Decode(&fc); err != nil {
40 return nil, err
41 }
42 case ".json":
43 if err := json.NewDecoder(fp).Decode(&fc); err != nil {
44 return nil, err
45 }
46 default:
47 return nil, fmt.Errorf("Config files with extension %s are not supported", e)
48 }
49
50 ck, ok := fc[key]
51 if !ok {
52 return nil, fmt.Errorf("Key %s does not exist in config file", key)
53 }
54
55 cfg := map[string]any{}
56 if err := mapstructure.Decode(ck, &cfg); err != nil {
57 return nil, fmt.Errorf("Config file key was not of correct type: %w", err)
58 }
59
60 return &ConfigFileNetboxClient{
61 cfg: cfg,
62 }, nil
63}
64
65// GetSitePrefixesWithTag gets a list of IP prefixes for a site based on
66// some tag.
67//
68// The data format of the config file should be, under the key "sites":
69//
70// map[string]map[string][]string{
71// "site_name": map[string][]string{
72// "tag_name": []string{
73// "127.0.0.1/24",
74// },
75// },
76// }
77//
78// In JSON format:
79//
80// { "sites": {
81// "site": {
82// "tag_name": [ "127.0.0.1/24" ]
83// }
84// }
85func (c *ConfigFileNetboxClient) GetSitePrefixesWithTag(ctx context.Context, site string, tag string) ([]*net.IPNet, error) {
86 s, ok := c.cfg["sites"]
87 if !ok {
88 return nil, fmt.Errorf("No key 'sites' in config file")
89 }
90
91 sites := map[string]map[string][]string{}
92 if err := mapstructure.Decode(s, &sites); err != nil {
93 return nil, fmt.Errorf("Key 'sites' not in correct format: %w", err)
94 }
95
96 tags, ok := sites[site]
97 if !ok {
98 return nil, fmt.Errorf("Site %s not in sites", site)
99 }
100
101 values, ok := tags[tag]
102 if !ok {
103 return nil, fmt.Errorf("Tag %s not in tags", tag)
104 }
105
106 return parseValues(values)
107}
108
109// GetPrefixesWithTag gets a list fo IP prefixes based on some tag.
110//
111// The data format of the config file should be, under the key "tags":
112//
113// map[string][]string{
114// "tag_name": []string{
115// "127.0.0.1/24",
116// },
117// }
118//
119// In JSON format:
120//
121// { "tags": { "tag_name": [ "127.0.0.1/24" ] } }
122func (c *ConfigFileNetboxClient) GetPrefixesWithTag(ctx context.Context, tag string) ([]*net.IPNet, error) {
123 t, ok := c.cfg["tags"]
124 if !ok {
125 return nil, fmt.Errorf("No key 'tags' in config file")
126 }
127
128 tags := map[string][]string{}
129 if err := mapstructure.Decode(t, &tags); err != nil {
130 return nil, fmt.Errorf("Key 'tags' not in correct format: %w", err)
131 }
132
133 values, ok := tags[tag]
134 if !ok {
135 return nil, fmt.Errorf("Tag %s not in tags", tag)
136 }
137
138 return parseValues(values)
139}
140
141func parseValues(v []string) ([]*net.IPNet, error) {
142 out := make([]*net.IPNet, len(v))
143 for i, n := range v {
144 _, in, err := net.ParseCIDR(n)
145 if err != nil {
146 return nil, err
147 }
148 out[i] = in
149 }
150 return out, nil
151}
diff --git a/clients/netbox/config_file_client_test.go b/clients/netbox/config_file_client_test.go
new file mode 100644
index 0000000..bce5165
--- /dev/null
+++ b/clients/netbox/config_file_client_test.go
@@ -0,0 +1,165 @@
1package netbox
2
3import (
4 "context"
5 "net"
6 "testing"
7 "testing/fstest"
8
9 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/suite"
11)
12
13var (
14 ipNet1 *net.IPNet
15 ipNet2 *net.IPNet
16)
17
18func init() {
19 _, ipNet1, _ = net.ParseCIDR("127.0.0.1/8")
20 _, ipNet2, _ = net.ParseCIDR("10.0.10.0/24")
21}
22
23var testFs fstest.MapFS
24
25var jsonMissingTopSiteTags = []byte(`{
26 "netbox": { }
27}`)
28
29var jsonInvalidFormat = []byte(`{
30 "netbox": {
31 "sites": {
32 "site1": {
33 "invalid-tag": { "foo": "bar" }
34 }
35 },
36 "tags": {
37 "invalid-tag": { "foo": "bar" }
38 }
39 }
40}`)
41
42var jsonTestConfig = []byte(`{
43 "netbox": {
44 "sites": {
45 "site1": {
46 "tag1": [ "127.0.0.1/8", "10.0.10.0/24" ]
47 }
48 },
49 "tags": {
50 "tag1": [ "127.0.0.1/8", "10.0.10.0/24" ]
51 }
52 }
53}`)
54
55var yamlTestConfig = []byte(`
56netbox:
57 sites:
58 site1:
59 tag1: [ "127.0.0.1/8", "10.0.10.0/24" ]
60 tags:
61 tag1: [ "127.0.0.1/8", "10.0.10.0/24" ]
62`)
63
64func init() {
65 testFs = fstest.MapFS{}
66 testFs["netbox.foo"] = &fstest.MapFile{Data: jsonTestConfig}
67 testFs["netbox.json"] = &fstest.MapFile{Data: jsonTestConfig}
68 testFs["netbox.yaml"] = &fstest.MapFile{Data: yamlTestConfig}
69 testFs["netbox.yml"] = &fstest.MapFile{Data: yamlTestConfig}
70 testFs["no-site-tags.json"] = &fstest.MapFile{Data: jsonMissingTopSiteTags}
71 testFs["invalid-format.json"] = &fstest.MapFile{Data: jsonInvalidFormat}
72}
73
74type ConfigFileNetboxClientSuite struct {
75 suite.Suite
76 c NetboxClient
77 nst NetboxClient
78 inv NetboxClient
79}
80
81func (s *ConfigFileNetboxClientSuite) SetupTest() {
82 var err error
83 s.c, err = NewConfigFileClient(testFs, "netbox.yaml", "netbox")
84 assert.NoError(s.T(), err)
85
86 s.nst, err = NewConfigFileClient(testFs, "no-site-tags.json", "netbox")
87 assert.NoError(s.T(), err)
88
89 s.inv, err = NewConfigFileClient(testFs, "invalid-format.json", "netbox")
90 assert.NoError(s.T(), err)
91}
92
93func (s *ConfigFileNetboxClientSuite) TestFromJSON() {
94 _, err := NewConfigFileClient(testFs, "netbox.json", "netbox")
95 assert.NoError(s.T(), err)
96}
97
98func (s *ConfigFileNetboxClientSuite) TestFromYAML() {
99 _, err := NewConfigFileClient(testFs, "netbox.yaml", "netbox")
100 assert.NoError(s.T(), err)
101 _, err = NewConfigFileClient(testFs, "netbox.yml", "netbox")
102 assert.NoError(s.T(), err)
103}
104
105func (s *ConfigFileNetboxClientSuite) TestFromInvalidExtension() {
106 _, err := NewConfigFileClient(testFs, "netbox.foo", "netbox")
107 assert.ErrorContains(s.T(), err, "extension .foo are not supported")
108}
109
110func (s *ConfigFileNetboxClientSuite) TestFromInvalidFile() {
111 _, err := NewConfigFileClient(testFs, "netbox.foo-", "netbox")
112 assert.ErrorContains(s.T(), err, "file does not exist")
113}
114
115func (s *ConfigFileNetboxClientSuite) TestFromInvalidKey() {
116 _, err := NewConfigFileClient(testFs, "netbox.json", "netboks")
117 assert.ErrorContains(s.T(), err, "Key netboks does not exist")
118}
119
120func (s *ConfigFileNetboxClientSuite) TestGetSitePrefixesWithTag() {
121 ips, err := s.c.GetSitePrefixesWithTag(context.TODO(), "site1", "tag1")
122 assert.NoError(s.T(), err)
123 assert.Equal(s.T(), ipNet1, ips[0])
124 assert.Equal(s.T(), ipNet2, ips[1])
125}
126
127func (s *ConfigFileNetboxClientSuite) TestGetSitePrefixesWithTagMissing() {
128 _, err := s.nst.GetSitePrefixesWithTag(context.TODO(), "site1", "tag1")
129 assert.ErrorContains(s.T(), err, "No key 'sites'")
130
131 _, err = s.c.GetSitePrefixesWithTag(context.TODO(), "site2", "tag1")
132 assert.ErrorContains(s.T(), err, "Site site2 not in sites")
133
134 _, err = s.c.GetSitePrefixesWithTag(context.TODO(), "site1", "tag2")
135 assert.ErrorContains(s.T(), err, "Tag tag2 not in tags")
136}
137
138func (s *ConfigFileNetboxClientSuite) TestGetSitePrefixesWithTagInvalid() {
139 _, err := s.inv.GetSitePrefixesWithTag(context.TODO(), "site1", "invalid-tag")
140 assert.ErrorContains(s.T(), err, "Key 'sites' not in correct format")
141}
142
143func (s *ConfigFileNetboxClientSuite) TestGetPrefixesWithTag() {
144 ips, err := s.c.GetPrefixesWithTag(context.TODO(), "tag1")
145 assert.NoError(s.T(), err)
146 assert.Equal(s.T(), ipNet1, ips[0])
147 assert.Equal(s.T(), ipNet2, ips[1])
148}
149
150func (s *ConfigFileNetboxClientSuite) TestGetPrefixesWithTagMissingTags() {
151 _, err := s.nst.GetPrefixesWithTag(context.TODO(), "tag1")
152 assert.ErrorContains(s.T(), err, "No key 'tags'")
153
154 _, err = s.c.GetPrefixesWithTag(context.TODO(), "tag2")
155 assert.ErrorContains(s.T(), err, "Tag tag2 not in tags")
156}
157
158func (s *ConfigFileNetboxClientSuite) TestGetPrefixesWithTagMissingInvalid() {
159 _, err := s.inv.GetPrefixesWithTag(context.TODO(), "invalid-tag")
160 assert.ErrorContains(s.T(), err, "Key 'tags' not in correct format")
161}
162
163func TestConfigFileNetboxClientSuite(t *testing.T) {
164 suite.Run(t, &ConfigFileNetboxClientSuite{})
165}
diff --git a/clients/netbox/go.mod b/clients/netbox/go.mod
index b1a9bf4..1a3ea97 100644
--- a/clients/netbox/go.mod
+++ b/clients/netbox/go.mod
@@ -1,3 +1,15 @@
1module code.crute.us/mcrute/golib/clients/netbox/v3 1module code.crute.us/mcrute/golib/clients/netbox/v3
2 2
3go 1.18 3go 1.18
4
5require (
6 github.com/mitchellh/mapstructure v1.5.0
7 github.com/stretchr/testify v1.8.1
8 gopkg.in/yaml.v2 v2.4.0
9)
10
11require (
12 github.com/davecgh/go-spew v1.1.1 // indirect
13 github.com/pmezard/go-difflib v1.0.0 // indirect
14 gopkg.in/yaml.v3 v3.0.1 // indirect
15)
diff --git a/clients/netbox/go.sum b/clients/netbox/go.sum
index e69de29..0468562 100644
--- a/clients/netbox/go.sum
+++ b/clients/netbox/go.sum
@@ -0,0 +1,21 @@
1github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
5github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
6github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
8github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
9github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
10github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
11github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
12github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
13github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
14github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
15gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
16gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
17gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
18gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
19gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
20gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
21gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=