-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathib_api.go
245 lines (211 loc) · 6.96 KB
/
ib_api.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
package main
import (
"encoding/json"
"fmt"
. "github.com/dirtman/sitepkg"
)
// On failure Infoblox WAPI requests return with a code of 400 or higher and
// include a body with additional detail. An object of type ibError can be
// be used for un-marshalling the body. Most of the time. For auth and perhaps
// other failures, an HTML string is returned.
type ibError struct {
Error string `json:"Error,omitempty"`
Code string `json:"code,omitempty"`
Text string `json:"text,omitempty"`
}
// IBAPIError implements the Error interface. It includes additional fields
// to ibError, and the "Error" field has been renamed to accommodate an
// "Error" method.
type IBAPIError struct {
ibError string // Infoblox "Error"
code string // Infoblox "code"
text string // Infoblox "text"
apiError string // err returned by lower layer.
requestURL string // The url request
method string // The request method
}
func (err *IBAPIError) Error() string {
if err.apiError == "" {
return ""
} else if err.text != "" {
if Verbose {
return fmt.Sprintf("%s: %s", err.apiError, err.ibError)
} else {
return err.text
}
} else if err.ibError != "" {
return fmt.Sprintf("%s: %s", err.apiError, err.ibError)
} else {
return err.apiError
}
}
// Infoblox-specific wrapper for API calls. Since Infoblox always returns a well
// defined error result in the body, we can provide the caller with more detail.
func IBAPIGet(url string) (body []byte, err *IBAPIError) {
return IBAPIRequest("GET", url, nil)
}
func IBAPIPost(url string, data interface{}) (body []byte, err *IBAPIError) {
return IBAPIRequest("POST", url, data)
}
func IBAPIPut(url string, data interface{}) (body []byte, err *IBAPIError) {
return IBAPIRequest("PUT", url, data)
}
func IBAPIDelete(url string, data interface{}) (body []byte, err *IBAPIError) {
return IBAPIRequest("DELETE", url, data)
}
func IBAPIRequest(method string, url string, data interface{}) ([]byte, *IBAPIError) {
body, apiErr := APIRequest(method, url, data)
if apiErr == nil {
return body, nil
}
ibErr := new(ibError)
ibapiErr := new(IBAPIError)
ibapiErr.ibError = "empty body returned for failed request"
ibapiErr.apiError = fmt.Sprintf("%v", apiErr)
ibapiErr.method = method
ibapiErr.requestURL = url
// If body is nil, let's just populate the error from upstream.
if body == nil {
return body, ibapiErr
} else if len(body) < 1 { // Hmmm, not sure what's going on here.
ibapiErr.ibError = "almost empty body returned for failed request"
return body, ibapiErr
} else if body[0] != 123 { // check if it looks like json; 123 == `{`
ibapiErr.ibError = string(body)
ibapiErr.text = fmt.Sprintf("%s", apiErr)
return body, ibapiErr
} else if err := json.Unmarshal(body, ibErr); err != nil {
ibapiErr.ibError = fmt.Sprintf("failure unmarshing body: %s", err)
return body, ibapiErr
}
ibapiErr.ibError = ibErr.Error
ibapiErr.text = ibErr.Text
ibapiErr.code = ibErr.Code
return body, ibapiErr
}
/*****************************************************************************\
Make a WAPI get request and return the raw JSON body.
object: the Infoblox "wapitype", such as "record:a" or "record:cname".
nKey: the name key for the type of object, most often "name".
dKey: the data key, such as "ipv4addr", "canonical", or "target_name".
sf: the set of fields to be included in the request, such as "zone=external".
rf: the desired return fields (_return_fields).
While the Infoblox WAPI does not point out a specific "data" field, ibapi
distinguishes a specific "data" field for each record type, such as "ipv4addr"
for A records, "target_name" for Alias records, "canonical" for CNAME records,
and so on.
"name" is most often the "name" field specified by Infoblox, but for PTR
records, for example, the ibapi "name" refers to the Infoblox record:ptr
"ptrdname", such as "rb4.rice.edu". I believe a user prefers to do a PTR
lookup via "rb4.rice.edu" (name), or "168.7.56.224" (data), as opposed to
"224.56.7.168.in-addr.arpa".
Separating out a data field and the name field from the rest of the fields
improves the user experience, especially when operationg on multiple records.
Likewise for "_return_fields".
\*****************************************************************************/
func getRecords(object, nKey, dKey, name, data string, sf, rf []string) ([]byte, error) {
ShowDebug("getRecords: name \"%s\".", name)
ShowDebug("getRecords: data \"%s\".", data)
ShowDebug("getRecords: sf \"%#v\".", sf)
ShowDebug("getRecords: rf \"%#v\".", rf)
if name == "" && data == "" && (len(sf) == 0) {
return nil, Error("no name, data or fields specified for GET request")
}
url := "/" + object
sep := "?"
if name != "" {
url += sep + nKey + "=" + name
sep = "&"
}
if data != "" {
url += sep + dKey + "=" + data
sep = "&"
}
if len(sf) > 0 {
for _, field := range sf {
url += sep + field
sep = "&"
}
}
if len(rf) > 0 {
url += sep + "_return_fields%2b" // %2b == '+'
sep = "="
for _, field := range rf {
url += sep + field
sep = ","
}
}
body, ibapiErr := IBAPIGet(url)
if ibapiErr != nil {
// return nil, Error("failure fetching \"%s\": %v", url, ibapiErr)
return nil, ibapiErr
} else if Verbose && !Debug {
Show("GET result for %s:\n%s\n", url, body)
}
return body, nil
}
// Add the specified record.
func addRecord(object, nKey, dKey, name, data string, f []string) ([]byte, error) {
// For Adds, a name is always required:
if name == "" {
return nil, Error("a name value must be provided")
}
// And unless dKey == "", a data value is also required:
if dKey != "" && data == "" {
return nil, Error("a data value must be provided for %s", dKey)
}
url := ""
sep := "&"
// The ZoneAuth object does not have a "data" field.
if dKey == "" {
url = "/" + object + "?" + nKey + "=" + name
} else {
url = "/" + object + "?" + nKey + "=" + name + sep + dKey + "=" + data
}
if len(f) > 0 {
for _, field := range f {
url += sep + field
}
}
body, ibapiErr := IBAPIPost(url, nil)
if ibapiErr != nil {
// return nil, Error("POST failure for \"%s\": %v", url, ibapiErr)
return nil, ibapiErr
}
ShowDebug("body: %s", body)
return body, nil
}
func updateRecord(ref string, f []string) ([]byte, error) {
url := "/" + ref
sep := "?"
if f == nil || len(f) < 1 {
return nil, Error("no fields to update")
}
for _, field := range f {
url += sep + field
sep = "&"
}
body, ibapiErr := IBAPIPut(url, nil)
if ibapiErr != nil {
// return nil, Error("PUT failure for \"%s\": %v", url, ibapiErr)
return nil, ibapiErr
}
ShowDebug("body: %s", body)
return body, nil
}
// Delete the specified record.
func deleteRecord(ref string, f []string) ([]byte, error) {
url := "/" + ref
sep := "?"
for _, field := range f {
url += sep + field
sep = "&"
}
body, ibapiErr := IBAPIDelete(url, nil)
if ibapiErr != nil {
// return nil, Error("DELETE failure for \"%s\": %v", url, ibapiErr)
return nil, ibapiErr
}
ShowDebug("body: %s", body)
return body, nil
}