github.com/infobloxopen/infoblox-go-client@v1.1.1/connector.go (about) 1 package ibclient 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 "crypto/x509" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "io/ioutil" 11 "log" 12 "net/http" 13 "net/http/cookiejar" 14 "net/url" 15 "reflect" 16 "strings" 17 "time" 18 19 "golang.org/x/net/publicsuffix" 20 ) 21 22 type HostConfig struct { 23 Host string 24 Version string 25 Port string 26 Username string 27 Password string 28 } 29 30 type TransportConfig struct { 31 SslVerify bool 32 certPool *x509.CertPool 33 HttpRequestTimeout time.Duration // in seconds 34 HttpPoolConnections int 35 ProxyUrl *url.URL 36 } 37 38 func NewTransportConfig(sslVerify string, httpRequestTimeout int, httpPoolConnections int) (cfg TransportConfig) { 39 switch { 40 case "false" == strings.ToLower(sslVerify): 41 cfg.SslVerify = false 42 case "true" == strings.ToLower(sslVerify): 43 cfg.SslVerify = true 44 default: 45 caPool := x509.NewCertPool() 46 cert, err := ioutil.ReadFile(sslVerify) 47 if err != nil { 48 log.Printf("Cannot load certificate file '%s'", sslVerify) 49 return 50 } 51 if !caPool.AppendCertsFromPEM(cert) { 52 err = fmt.Errorf("Cannot append certificate from file '%s'", sslVerify) 53 return 54 } 55 cfg.certPool = caPool 56 cfg.SslVerify = true 57 } 58 59 cfg.HttpPoolConnections = httpPoolConnections 60 cfg.HttpRequestTimeout = time.Duration(httpRequestTimeout) 61 return 62 } 63 64 type HttpRequestBuilder interface { 65 Init(HostConfig) 66 BuildUrl(r RequestType, objType string, ref string, returnFields []string, queryParams QueryParams) (urlStr string) 67 BuildBody(r RequestType, obj IBObject) (jsonStr []byte) 68 BuildRequest(r RequestType, obj IBObject, ref string, queryParams QueryParams) (req *http.Request, err error) 69 } 70 71 type HttpRequestor interface { 72 Init(TransportConfig) 73 SendRequest(*http.Request) ([]byte, error) 74 } 75 76 type WapiRequestBuilder struct { 77 HostConfig HostConfig 78 } 79 80 type WapiHttpRequestor struct { 81 client http.Client 82 } 83 84 type IBConnector interface { 85 CreateObject(obj IBObject) (ref string, err error) 86 GetObject(obj IBObject, ref string, res interface{}) error 87 DeleteObject(ref string) (refRes string, err error) 88 UpdateObject(obj IBObject, ref string) (refRes string, err error) 89 } 90 91 type Connector struct { 92 HostConfig HostConfig 93 TransportConfig TransportConfig 94 RequestBuilder HttpRequestBuilder 95 Requestor HttpRequestor 96 } 97 98 type RequestType int 99 100 const ( 101 CREATE RequestType = iota 102 GET 103 DELETE 104 UPDATE 105 ) 106 107 func (r RequestType) toMethod() string { 108 switch r { 109 case CREATE: 110 return "POST" 111 case GET: 112 return "GET" 113 case DELETE: 114 return "DELETE" 115 case UPDATE: 116 return "PUT" 117 } 118 119 return "" 120 } 121 122 func getHTTPResponseError(resp *http.Response) error { 123 defer resp.Body.Close() 124 content, _ := ioutil.ReadAll(resp.Body) 125 msg := fmt.Sprintf("WAPI request error: %d('%s')\nContents:\n%s\n", resp.StatusCode, resp.Status, content) 126 log.Printf(msg) 127 return errors.New(msg) 128 } 129 130 func (whr *WapiHttpRequestor) Init(cfg TransportConfig) { 131 tr := &http.Transport{ 132 TLSClientConfig: &tls.Config{InsecureSkipVerify: !cfg.SslVerify, 133 RootCAs: cfg.certPool, 134 Renegotiation: tls.RenegotiateOnceAsClient}, 135 MaxIdleConnsPerHost: cfg.HttpPoolConnections, 136 } 137 138 if cfg.ProxyUrl != nil { 139 tr.Proxy = http.ProxyURL(cfg.ProxyUrl) 140 } 141 142 // All users of cookiejar should import "golang.org/x/net/publicsuffix" 143 jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) 144 if err != nil { 145 log.Fatal(err) 146 } 147 148 whr.client = http.Client{Jar: jar, Transport: tr, Timeout: cfg.HttpRequestTimeout * time.Second} 149 } 150 151 func (whr *WapiHttpRequestor) SendRequest(req *http.Request) (res []byte, err error) { 152 var resp *http.Response 153 resp, err = whr.client.Do(req) 154 if err != nil { 155 return 156 } else if !(resp.StatusCode == http.StatusOK || 157 (resp.StatusCode == http.StatusCreated && 158 req.Method == RequestType(CREATE).toMethod())) { 159 err := getHTTPResponseError(resp) 160 return nil, err 161 } 162 defer resp.Body.Close() 163 res, err = ioutil.ReadAll(resp.Body) 164 if err != nil { 165 log.Printf("Http Reponse ioutil.ReadAll() Error: '%s'", err) 166 return 167 } 168 169 return 170 } 171 172 func (wrb *WapiRequestBuilder) Init(cfg HostConfig) { 173 wrb.HostConfig = cfg 174 } 175 176 func (wrb *WapiRequestBuilder) BuildUrl(t RequestType, objType string, ref string, returnFields []string, queryParams QueryParams) (urlStr string) { 177 path := []string{"wapi", "v" + wrb.HostConfig.Version} 178 if len(ref) > 0 { 179 path = append(path, ref) 180 } else { 181 path = append(path, objType) 182 } 183 184 qry := "" 185 vals := url.Values{} 186 if t == GET { 187 if len(returnFields) > 0 { 188 vals.Set("_return_fields", strings.Join(returnFields, ",")) 189 } 190 // TODO need to get this from individual objects in future 191 if queryParams.forceProxy { 192 vals.Set("_proxy_search", "GM") 193 } 194 qry = vals.Encode() 195 } 196 197 u := url.URL{ 198 Scheme: "https", 199 Host: wrb.HostConfig.Host + ":" + wrb.HostConfig.Port, 200 Path: strings.Join(path, "/"), 201 RawQuery: qry, 202 } 203 204 return u.String() 205 } 206 207 func (wrb *WapiRequestBuilder) BuildBody(t RequestType, obj IBObject) []byte { 208 var objJSON []byte 209 var err error 210 211 objJSON, err = json.Marshal(obj) 212 if err != nil { 213 log.Printf("Cannot marshal object '%s': %s", obj, err) 214 return nil 215 } 216 217 eaSearch := obj.EaSearch() 218 if t == GET && len(eaSearch) > 0 { 219 eaSearchJSON, err := json.Marshal(eaSearch) 220 if err != nil { 221 log.Printf("Cannot marshal EA Search attributes. '%s'\n", err) 222 return nil 223 } 224 objJSON = append(append(objJSON[:len(objJSON)-1], byte(',')), eaSearchJSON[1:]...) 225 } 226 227 return objJSON 228 } 229 230 func (wrb *WapiRequestBuilder) BuildRequest(t RequestType, obj IBObject, ref string, queryParams QueryParams) (req *http.Request, err error) { 231 var ( 232 objType string 233 returnFields []string 234 ) 235 if obj != nil { 236 objType = obj.ObjectType() 237 returnFields = obj.ReturnFields() 238 } 239 urlStr := wrb.BuildUrl(t, objType, ref, returnFields, queryParams) 240 241 var bodyStr []byte 242 if obj != nil { 243 bodyStr = wrb.BuildBody(t, obj) 244 } 245 246 req, err = http.NewRequest(t.toMethod(), urlStr, bytes.NewBuffer(bodyStr)) 247 if err != nil { 248 log.Printf("err1: '%s'", err) 249 return 250 } 251 req.Header.Set("Content-Type", "application/json") 252 req.SetBasicAuth(wrb.HostConfig.Username, wrb.HostConfig.Password) 253 254 return 255 } 256 257 func (c *Connector) makeRequest(t RequestType, obj IBObject, ref string, queryParams QueryParams) (res []byte, err error) { 258 var req *http.Request 259 req, err = c.RequestBuilder.BuildRequest(t, obj, ref, queryParams) 260 res, err = c.Requestor.SendRequest(req) 261 if err != nil { 262 /* Forcing the request to redirect to Grid Master by making forcedProxy=true */ 263 queryParams.forceProxy = true 264 req, err = c.RequestBuilder.BuildRequest(t, obj, ref, queryParams) 265 res, err = c.Requestor.SendRequest(req) 266 } 267 268 return 269 } 270 271 func (c *Connector) CreateObject(obj IBObject) (ref string, err error) { 272 ref = "" 273 queryParams := QueryParams{forceProxy: false} 274 resp, err := c.makeRequest(CREATE, obj, "", queryParams) 275 if err != nil || len(resp) == 0 { 276 log.Printf("CreateObject request error: '%s'\n", err) 277 return 278 } 279 280 err = json.Unmarshal(resp, &ref) 281 if err != nil { 282 log.Printf("Cannot unmarshall '%s', err: '%s'\n", string(resp), err) 283 return 284 } 285 286 return 287 } 288 289 func (c *Connector) GetObject(obj IBObject, ref string, res interface{}) (err error) { 290 queryParams := QueryParams{forceProxy: false} 291 resp, err := c.makeRequest(GET, obj, ref, queryParams) 292 //to check empty underlying value of interface 293 var result interface{} 294 err = json.Unmarshal(resp, &result) 295 if err != nil { 296 log.Printf("Cannot unmarshall to check empty value '%s', err: '%s'\n", string(resp), err) 297 } 298 299 var data []interface{} 300 if resp == nil || (reflect.TypeOf(result) == reflect.TypeOf(data) && len(result.([]interface{})) == 0) { 301 queryParams.forceProxy = true 302 resp, err = c.makeRequest(GET, obj, ref, queryParams) 303 } 304 if err != nil { 305 log.Printf("GetObject request error: '%s'\n", err) 306 } 307 if len(resp) == 0 { 308 return 309 } 310 err = json.Unmarshal(resp, res) 311 if err != nil { 312 log.Printf("Cannot unmarshall '%s', err: '%s'\n", string(resp), err) 313 return 314 } 315 return 316 } 317 318 func (c *Connector) DeleteObject(ref string) (refRes string, err error) { 319 refRes = "" 320 queryParams := QueryParams{forceProxy: false} 321 resp, err := c.makeRequest(DELETE, nil, ref, queryParams) 322 if err != nil { 323 log.Printf("DeleteObject request error: '%s'\n", err) 324 return 325 } 326 327 err = json.Unmarshal(resp, &refRes) 328 if err != nil { 329 log.Printf("Cannot unmarshall '%s', err: '%s'\n", string(resp), err) 330 return 331 } 332 333 return 334 } 335 336 func (c *Connector) UpdateObject(obj IBObject, ref string) (refRes string, err error) { 337 queryParams := QueryParams{forceProxy: false} 338 refRes = "" 339 resp, err := c.makeRequest(UPDATE, obj, ref, queryParams) 340 if err != nil { 341 log.Printf("Failed to update object %s: %s", obj.ObjectType(), err) 342 return 343 } 344 345 err = json.Unmarshal(resp, &refRes) 346 if err != nil { 347 log.Printf("Cannot unmarshall update object response'%s', err: '%s'\n", string(resp), err) 348 return 349 } 350 return 351 } 352 353 // Logout sends a request to invalidate the ibapauth cookie and should 354 // be used in a defer statement after the Connector has been successfully 355 // initialized. 356 func (c *Connector) Logout() (err error) { 357 queryParams := QueryParams{forceProxy: false} 358 _, err = c.makeRequest(CREATE, nil, "logout", queryParams) 359 if err != nil { 360 log.Printf("Logout request error: '%s'\n", err) 361 } 362 363 return 364 } 365 366 var ValidateConnector = validateConnector 367 368 func validateConnector(c *Connector) (err error) { 369 // GET UserProfile request is used here to validate connector's basic auth and reachability. 370 var response []UserProfile 371 userprofile := NewUserProfile(UserProfile{}) 372 err = c.GetObject(userprofile, "", &response) 373 if err != nil { 374 log.Printf("Failed to connect to the Grid, err: %s \n", err) 375 } 376 return 377 } 378 379 func NewConnector(hostConfig HostConfig, transportConfig TransportConfig, 380 requestBuilder HttpRequestBuilder, requestor HttpRequestor) (res *Connector, err error) { 381 res = nil 382 383 connector := &Connector{ 384 HostConfig: hostConfig, 385 TransportConfig: transportConfig, 386 } 387 388 //connector.RequestBuilder = WapiRequestBuilder{WaipHostConfig: connector.HostConfig} 389 connector.RequestBuilder = requestBuilder 390 connector.RequestBuilder.Init(connector.HostConfig) 391 392 connector.Requestor = requestor 393 connector.Requestor.Init(connector.TransportConfig) 394 395 res = connector 396 err = ValidateConnector(connector) 397 return 398 }