aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2023-09-27 16:55:23 -0700
committerMike Crute <mike@crute.us>2023-09-27 16:55:54 -0700
commit02f5fbcdd06c50d13c4f4df09fc29ca479dd492b (patch)
tree6bed705b6775f211273e595216c2f7aeca9d4f6d
parentf285d4b0d9018a5b256bcbb729c08bc87f09549e (diff)
downloadgolib-02f5fbcdd06c50d13c4f4df09fc29ca479dd492b.tar.bz2
golib-02f5fbcdd06c50d13c4f4df09fc29ca479dd492b.tar.xz
golib-02f5fbcdd06c50d13c4f4df09fc29ca479dd492b.zip
netbox: refactor out HTTP clientclients/netbox/v4.0.0
-rw-r--r--clients/netbox/client.go70
-rw-r--r--clients/netbox/go.mod4
-rw-r--r--clients/netbox/gql_dns_servers.go47
-rw-r--r--clients/netbox/http.go174
-rw-r--r--clients/netbox/model.go7
5 files changed, 243 insertions, 59 deletions
diff --git a/clients/netbox/client.go b/clients/netbox/client.go
index ad2967a..3b7fbaf 100644
--- a/clients/netbox/client.go
+++ b/clients/netbox/client.go
@@ -2,12 +2,8 @@ package netbox
2 2
3import ( 3import (
4 "context" 4 "context"
5 "encoding/json"
6 "fmt" 5 "fmt"
7 "io"
8 "net" 6 "net"
9 "net/http"
10 "net/url"
11 "strconv" 7 "strconv"
12) 8)
13 9
@@ -18,49 +14,11 @@ type NetboxClient interface {
18} 14}
19 15
20type BasicNetboxClient struct { 16type BasicNetboxClient struct {
21 ApiKey string 17 *NetboxHttpClient
22 Endpoint string
23} 18}
24 19
25var _ NetboxClient = (*BasicNetboxClient)(nil) 20func MustNewBasicNetboxClient(apiKey, endpoint string) NetboxClient {
26 21 return &BasicNetboxClient{MustNewNetboxHttpClient(apiKey, endpoint)}
27func (c *BasicNetboxClient) makeRequestRaw(ctx context.Context, method, u string, ib io.Reader, o interface{}) error {
28 req, err := http.NewRequestWithContext(ctx, method, u, ib)
29 if err != nil {
30 return err
31 }
32 req.Header.Add("Authorization", fmt.Sprintf("Token %s", c.ApiKey))
33
34 res, err := http.DefaultClient.Do(req)
35 if err != nil {
36 return err
37 }
38 defer res.Body.Close()
39
40 if res.StatusCode != http.StatusOK {
41 apiError := &ApiError{Status: res.StatusCode}
42 if err = json.NewDecoder(res.Body).Decode(apiError); err != nil {
43 return fmt.Errorf("Netbox JSON decode error while parsing error with status %d: %w", res.StatusCode, err)
44 }
45 return apiError
46 }
47
48 if err = json.NewDecoder(res.Body).Decode(o); err != nil {
49 return err
50 }
51
52 return nil
53}
54
55func (c *BasicNetboxClient) makeRequest(ctx context.Context, method, path string, q url.Values, ib io.Reader, o interface{}) error {
56 u, err := url.Parse(c.Endpoint)
57 if err != nil {
58 return err
59 }
60 u.Path = path
61 u.RawQuery = q.Encode()
62
63 return c.makeRequestRaw(ctx, method, u.String(), ib, o)
64} 22}
65 23
66func (c *BasicNetboxClient) GetSitePrefixesWithTag(ctx context.Context, site string, tag string) ([]*net.IPNet, error) { 24func (c *BasicNetboxClient) GetSitePrefixesWithTag(ctx context.Context, site string, tag string) ([]*net.IPNet, error) {
@@ -71,12 +29,12 @@ func (c *BasicNetboxClient) GetSitePrefixesWithTag(ctx context.Context, site str
71 29
72 out := []*net.IPNet{} 30 out := []*net.IPNet{}
73 31
74 q := url.Values{} 32 q := NewNetboxGetRequest("/api/ipam/prefixes/")
75 q.Add("site_id", strconv.Itoa(s)) 33 q.Add("site_id", strconv.Itoa(s))
76 q.Add("tag", tag) 34 q.Add("tag", tag)
77 35
78 page := &PrefixList{} 36 page := &PrefixList{}
79 if err := c.makeRequest(ctx, http.MethodGet, "/api/ipam/prefixes/", q, nil, page); err != nil { 37 if err := c.Do(ctx, q, page); err != nil {
80 return nil, err 38 return nil, err
81 } 39 }
82 40
@@ -90,7 +48,7 @@ func (c *BasicNetboxClient) GetSitePrefixesWithTag(ctx context.Context, site str
90 48
91 for page.Next != "" { 49 for page.Next != "" {
92 page = &PrefixList{} 50 page = &PrefixList{}
93 if err := c.makeRequestRaw(ctx, http.MethodGet, page.Next, nil, page); err != nil { 51 if err := c.Do(ctx, &NetboxRawGet{page.Next}, page); err != nil {
94 return nil, err 52 return nil, err
95 } 53 }
96 54
@@ -109,11 +67,11 @@ func (c *BasicNetboxClient) GetSitePrefixesWithTag(ctx context.Context, site str
109func (c *BasicNetboxClient) GetPrefixesWithTag(ctx context.Context, tag string) ([]*net.IPNet, error) { 67func (c *BasicNetboxClient) GetPrefixesWithTag(ctx context.Context, tag string) ([]*net.IPNet, error) {
110 out := []*net.IPNet{} 68 out := []*net.IPNet{}
111 69
112 q := url.Values{} 70 q := NewNetboxGetRequest("/api/ipam/prefixes/")
113 q.Add("tag", tag) 71 q.Add("tag", tag)
114 72
115 page := &PrefixList{} 73 page := &PrefixList{}
116 if err := c.makeRequest(ctx, http.MethodGet, "/api/ipam/prefixes/", q, nil, page); err != nil { 74 if err := c.Do(ctx, q, page); err != nil {
117 return nil, err 75 return nil, err
118 } 76 }
119 77
@@ -127,7 +85,7 @@ func (c *BasicNetboxClient) GetPrefixesWithTag(ctx context.Context, tag string)
127 85
128 for page.Next != "" { 86 for page.Next != "" {
129 page = &PrefixList{} 87 page = &PrefixList{}
130 if err := c.makeRequestRaw(ctx, http.MethodGet, page.Next, nil, page); err != nil { 88 if err := c.Do(ctx, &NetboxRawGet{page.Next}, page); err != nil {
131 return nil, err 89 return nil, err
132 } 90 }
133 91
@@ -144,11 +102,11 @@ func (c *BasicNetboxClient) GetPrefixesWithTag(ctx context.Context, tag string)
144} 102}
145 103
146func (c *BasicNetboxClient) resolveSiteNameToId(ctx context.Context, s string) (int, error) { 104func (c *BasicNetboxClient) resolveSiteNameToId(ctx context.Context, s string) (int, error) {
147 q := url.Values{} 105 q := NewNetboxGetRequest("/api/dcim/sites/")
148 q.Add("name", s) 106 q.Add("name", s)
149 107
150 out := &SiteList{} 108 out := &SiteList{}
151 if err := c.makeRequest(ctx, http.MethodGet, "/api/dcim/sites/", q, nil, out); err != nil { 109 if err := c.Do(ctx, q, out); err != nil {
152 return 0, err 110 return 0, err
153 } 111 }
154 112
@@ -162,11 +120,11 @@ func (c *BasicNetboxClient) resolveSiteNameToId(ctx context.Context, s string) (
162func (c *BasicNetboxClient) GetServicesForVm(ctx context.Context, vmName string) ([]*Service, error) { 120func (c *BasicNetboxClient) GetServicesForVm(ctx context.Context, vmName string) ([]*Service, error) {
163 out := []*Service{} 121 out := []*Service{}
164 122
165 q := url.Values{} 123 q := NewNetboxGetRequest("/api/ipam/services/")
166 q.Add("virtual_machine", vmName) 124 q.Add("virtual_machine", vmName)
167 125
168 page := &ServiceList{} 126 page := &ServiceList{}
169 if err := c.makeRequest(ctx, http.MethodGet, "/api/ipam/services/", q, nil, page); err != nil { 127 if err := c.Do(ctx, q, page); err != nil {
170 return nil, err 128 return nil, err
171 } 129 }
172 130
@@ -176,7 +134,7 @@ func (c *BasicNetboxClient) GetServicesForVm(ctx context.Context, vmName string)
176 134
177 for page.Next != "" { 135 for page.Next != "" {
178 page = &ServiceList{} 136 page = &ServiceList{}
179 if err := c.makeRequestRaw(ctx, http.MethodGet, page.Next, nil, page); err != nil { 137 if err := c.Do(ctx, &NetboxRawGet{page.Next}, page); err != nil {
180 return nil, err 138 return nil, err
181 } 139 }
182 140
diff --git a/clients/netbox/go.mod b/clients/netbox/go.mod
index 1a3ea97..8a66bed 100644
--- a/clients/netbox/go.mod
+++ b/clients/netbox/go.mod
@@ -1,6 +1,6 @@
1module code.crute.us/mcrute/golib/clients/netbox/v3 1module code.crute.us/mcrute/golib/clients/netbox/v4
2 2
3go 1.18 3go 1.20
4 4
5require ( 5require (
6 github.com/mitchellh/mapstructure v1.5.0 6 github.com/mitchellh/mapstructure v1.5.0
diff --git a/clients/netbox/gql_dns_servers.go b/clients/netbox/gql_dns_servers.go
new file mode 100644
index 0000000..67b4ad6
--- /dev/null
+++ b/clients/netbox/gql_dns_servers.go
@@ -0,0 +1,47 @@
1package netbox
2
3const dnsServerGQLQuery = `fragment VMHostDetails on VMInterfaceType{
4 virtual_machine {
5 site {
6 name
7 }
8 }
9}
10
11fragment HostDetails on InterfaceType {
12 device {
13 site {
14 name
15 }
16 }
17}
18
19query {
20 ip_address_list(tag:"dns-server") {
21 address
22 assigned_object{
23 ...VMHostDetails
24 ...HostDetails
25 }
26 }
27}`
28
29type dnsServerGQLResponse struct {
30 Data struct {
31 AddressList []struct {
32 Address string `json:"address"`
33 AssignedObject struct {
34 VirtualMachine struct {
35 Site struct {
36 Name string `json:"name"`
37 } `json:"site"`
38 } `json:"virtual_machine"`
39 Device struct {
40 Site struct {
41 Name string `json:"name"`
42 } `json:"site"`
43 } `json:"device"`
44 } `json:"assigned_object"`
45 } `json:"ip_address_list"`
46 } `json:"data"`
47}
diff --git a/clients/netbox/http.go b/clients/netbox/http.go
new file mode 100644
index 0000000..21a2b4b
--- /dev/null
+++ b/clients/netbox/http.go
@@ -0,0 +1,174 @@
1package netbox
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "net/http"
9 "net/url"
10)
11
12// NetboxHttpClient is an HTTP client for the Netbox API. It is very
13// low-level and should not be consumed by most clients. Instead use a
14// client implementing NetboxClient.
15type NetboxHttpClient struct {
16 ApiKey string
17 Endpoint url.URL
18}
19
20func MustNewNetboxHttpClient(apiKey, endpoint string) *NetboxHttpClient {
21 u, err := url.Parse(endpoint)
22 if err != nil {
23 panic(err)
24 }
25 return &NetboxHttpClient{apiKey, *u}
26}
27
28func (c *NetboxHttpClient) Do(ctx context.Context, r NetboxRequest, out any) error {
29 u := c.Endpoint // Store a local copy, requests may mutate it
30 req, err := r.BuildRequest(ctx, &u)
31 if err != nil {
32 return err
33 }
34
35 req.Header.Add("Authorization", fmt.Sprintf("Token %s", c.ApiKey))
36
37 res, err := http.DefaultClient.Do(req)
38 if err != nil {
39 return err
40 }
41 defer res.Body.Close()
42
43 if res.StatusCode != http.StatusOK {
44 apiError := &ApiError{Status: res.StatusCode}
45 if err = json.NewDecoder(res.Body).Decode(apiError); err != nil {
46 return fmt.Errorf("Netbox JSON decode error while parsing error with status %d: %w", res.StatusCode, err)
47 }
48 return apiError
49 }
50
51 if out != nil {
52 return json.NewDecoder(res.Body).Decode(out)
53 }
54
55 return nil
56}
57
58type NetboxRequest interface {
59 BuildRequest(context.Context, *url.URL) (*http.Request, error)
60}
61
62type NetboxGraphQLRequest struct {
63 Query string
64}
65
66var _ NetboxRequest = (*NetboxGraphQLRequest)(nil)
67
68func (r *NetboxGraphQLRequest) BuildRequest(ctx context.Context, host *url.URL) (*http.Request, error) {
69 q, err := json.Marshal(map[string]string{"query": r.Query})
70 if err != nil {
71 return nil, err
72 }
73
74 host.Path = "/graphql/"
75
76 req, err := http.NewRequestWithContext(
77 ctx,
78 http.MethodPost,
79 host.String(),
80 bytes.NewBuffer(q),
81 )
82 if err != nil {
83 return nil, err
84 }
85
86 req.Header.Add("Content-Type", "application/json")
87 req.Header.Add("Accept", "application/json")
88
89 return req, nil
90}
91
92type NetboxGetRequest struct {
93 url.Values
94 Path string
95}
96
97var _ NetboxRequest = (*NetboxGetRequest)(nil)
98
99func NewNetboxGetRequest(path string) *NetboxGetRequest {
100 return &NetboxGetRequest{url.Values{}, path}
101}
102
103func (r *NetboxGetRequest) BuildRequest(ctx context.Context, host *url.URL) (*http.Request, error) {
104 host.Path = r.Path
105 host.RawQuery = r.Encode()
106
107 req, err := http.NewRequestWithContext(
108 ctx,
109 http.MethodGet,
110 host.String(),
111 nil,
112 )
113 if err != nil {
114 return nil, err
115 }
116
117 req.Header.Add("Accept", "application/json")
118
119 return req, nil
120}
121
122type NetboxRawGet struct {
123 Url string
124}
125
126var _ NetboxRequest = (*NetboxRawGet)(nil)
127
128func (r *NetboxRawGet) BuildRequest(ctx context.Context, host *url.URL) (*http.Request, error) {
129 req, err := http.NewRequestWithContext(
130 ctx,
131 http.MethodGet,
132 r.Url,
133 nil,
134 )
135 if err != nil {
136 return nil, err
137 }
138
139 req.Header.Add("Accept", "application/json")
140
141 return req, nil
142}
143
144type NetboxJsonRequest struct {
145 Path string
146 Method string
147 Body any
148}
149
150var _ NetboxRequest = (*NetboxJsonRequest)(nil)
151
152func (r *NetboxJsonRequest) BuildRequest(ctx context.Context, host *url.URL) (*http.Request, error) {
153 host.Path = r.Path
154
155 body, err := json.Marshal(r.Body)
156 if err != nil {
157 return nil, err
158 }
159
160 req, err := http.NewRequestWithContext(
161 ctx,
162 r.Method,
163 host.String(),
164 bytes.NewBuffer(body),
165 )
166 if err != nil {
167 return nil, err
168 }
169
170 req.Header.Add("Content-Type", "application/json")
171 req.Header.Add("Accept", "application/json")
172
173 return req, nil
174}
diff --git a/clients/netbox/model.go b/clients/netbox/model.go
index be4767d..6ff0e6c 100644
--- a/clients/netbox/model.go
+++ b/clients/netbox/model.go
@@ -8,7 +8,12 @@ import (
8 8
9type ApiError struct { 9type ApiError struct {
10 Status int 10 Status int
11 Detail string `json:"detail"` 11 Detail string `json:"detail"`
12 Errors []ApiSubError `json:"errors"`
13}
14
15type ApiSubError struct {
16 Message string `json:"message"`
12} 17}
13 18
14func (e *ApiError) Error() string { 19func (e *ApiError) Error() string {