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  }