-
Notifications
You must be signed in to change notification settings - Fork 26
/
resource.go
251 lines (212 loc) · 6.72 KB
/
resource.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
246
247
248
249
250
251
package infoblox
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/url"
"strconv"
"strings"
)
//Resource represents a WAPI object type
type Resource struct {
conn *Client
wapiObject string
}
// NewResource creates new resource object from the passed parameters.
// As Infoblox contains hundreds of different objects, this function
// will allow the users to adopt new ones vary easy.
func NewResource(c *Client, wapiObject string) *Resource {
return &Resource{
conn: c,
wapiObject: wapiObject,
}
}
// Options represents the Options to be passed to the Infoblox WAPI
type Options struct {
//The maximum number of objects to be returned. If set to a negative
//number the appliance will return an error when the number of returned
//objects would exceed the setting. The default is -1000. If this is
//set to a positive number, the results will be truncated when necessary.
MaxResults *int
ReturnFields []string //A list of returned fields
ReturnBasicFields bool // Return basic fields in addition to ReturnFields
}
// A Condition is used for searching
type Condition struct {
Field *string // EITHER A documented field of the object (only set one)
Attribute *string // OR the name of an extensible attribute (only set one)
Modifiers string // Optional search modifiers "!:~<>" (otherwise exact match)
Value string // Value or regular expression to search for
}
// All returns an array of all records for this resource
func (r Resource) All(opts *Options) ([]map[string]interface{}, error) {
return r.Find([]Condition{}, opts)
}
func (r Resource) Delete(ref string) (string, error) {
uri := r.resourceBase() + ref
resp, err := r.conn.SendRequest("DELETE", uri, "", nil)
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
buf.ReadFrom(resp.Body)
bodyOut := buf.String()
return bodyOut, nil
}
// Find resources with query parameters. Conditions are combined with AND
// logic. When a field is a list of extensible attribute that can have multiple
// values, the condition is true if any value in the list matches.
func (r Resource) Find(query []Condition, opts *Options) ([]map[string]interface{}, error) {
resp, err := r.find(query, opts)
if err != nil {
return nil, err
}
var out []map[string]interface{}
err = resp.Parse(&out)
if err != nil {
return nil, fmt.Errorf("%+v", err)
}
return out, nil
}
// Query retrieves objects from an Infoblox instance that meet specific
// conditions and options. The return object is defined by the user and
// passed to the function by the "out" parameter.
func (r Resource) Query(query []Condition, opts *Options, out interface{}) error {
resp, err := r.find(query, opts)
if err != nil {
return err
}
err = resp.Parse(&out)
if err != nil {
return fmt.Errorf("%+v", err)
}
return nil
}
func (r Resource) find(query []Condition, opts *Options) (*APIResponse, error) {
q := r.getQuery(opts, query, url.Values{})
resp, err := r.conn.SendRequest("GET", r.resourceURI()+"?"+q.Encode(), "", nil)
if err != nil {
return nil, fmt.Errorf("Error sending request: %v", err)
}
return resp, nil
}
type APIErrorResponse struct {
Text string `json:"text"`
}
func (r Resource) JsonAction(url string, actionType string, data string) (*APIResponse, error) {
var err error
head := make(map[string]string)
if err != nil {
return nil, fmt.Errorf("Error creating request: %v\n", err)
}
head["Content-Type"] = "application/json"
resp, err := r.conn.SendRequest(actionType, url, data, head)
return resp, err
}
func (r Resource) CreateJson(url string, opts *Options, data []byte) (string, error) {
var urlStr, bodyJSON string
urlStr = r.resourceURI()
bodyJSON = string(data[:])
resp, _ := r.JsonAction(urlStr, "POST", bodyJSON)
if resp.StatusCode == 400 {
var t APIErrorResponse
body, _ := ioutil.ReadAll(resp.Body)
json.Unmarshal(body, &t)
return t.Text, errors.New(t.Text)
} else {
body, _ := ioutil.ReadAll(resp.Body)
return string(body[:]), nil
}
}
func (r Resource) UpdateJson(url string, opts *Options, data []byte) (string, error) {
var urlStr, bodyJSON string
urlStr = r.resourceBase() + url
bodyJSON = string(data[:])
resp, _ := r.JsonAction(urlStr, "PUT", bodyJSON)
if resp.StatusCode == 400 {
var t APIErrorResponse
body, _ := ioutil.ReadAll(resp.Body)
json.Unmarshal(body, &t)
return t.Text, errors.New(t.Text)
} else {
body, _ := ioutil.ReadAll(resp.Body)
return string(body[:]), nil
}
}
// Create creates a resource. Returns the ref of the created resource.
func (r Resource) Create(data url.Values, opts *Options, body interface{}) (string, error) {
q := r.getQuery(opts, []Condition{}, data)
q.Set("_return_fields", "") //Force object response
var err error
head := make(map[string]string)
var bodyStr, urlStr string
if body == nil {
// Send URL-encoded data in the request body
urlStr = r.resourceURI()
bodyStr = q.Encode()
head["Content-Type"] = "application/x-www-form-urlencoded"
} else {
// Put url-encoded data in the URL and send the body parameter as a JSON body.
bodyJSON, err := json.Marshal(body)
if err != nil {
return "", fmt.Errorf("Error creating request: %v", err)
}
log.Printf("POST body: %s\n", bodyJSON)
urlStr = r.resourceURI() + "?" + q.Encode()
bodyStr = string(bodyJSON)
head["Content-Type"] = "application/json"
}
resp, err := r.conn.SendRequest("POST", urlStr, bodyStr, head)
if err != nil {
return "", fmt.Errorf("Error sending request: %v", err)
}
//fmt.Printf("%v", resp.ReadBody())
// If you POST to record:host with a scheduled creation time, it sends back a string regardless of the presence of _return_fields
var responseData interface{}
var ret string
if err := resp.Parse(&responseData); err != nil {
return "", fmt.Errorf("%+v", err)
}
switch s := responseData.(type) {
case string:
ret = s
case map[string]interface{}:
ret = s["_ref"].(string)
default:
return "", fmt.Errorf("Invalid return type %T", s)
}
return ret, nil
}
func (r Resource) getQuery(opts *Options, query []Condition, extra url.Values) url.Values {
v := extra
returnFieldOption := "_return_fields"
if opts != nil && opts.ReturnBasicFields {
returnFieldOption = "_return_fields+"
}
if opts != nil && opts.ReturnFields != nil {
v.Set(returnFieldOption, strings.Join(opts.ReturnFields, ","))
}
if opts != nil && opts.MaxResults != nil {
v.Set("_max_results", strconv.Itoa(*opts.MaxResults))
}
for _, cond := range query {
search := ""
if cond.Field != nil {
search += *cond.Field
} else if cond.Attribute != nil {
search += "*" + *cond.Attribute
}
search += cond.Modifiers
v.Set(search, cond.Value)
}
return v
}
func (r Resource) resourceBase() string {
return BasePath
}
func (r Resource) resourceURI() string {
return BasePath + r.wapiObject
}