terraform/vendor/github.com/henrikhodne/go-librato/librato/client.go

288 lines
6.9 KiB
Go

package librato
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"github.com/google/go-querystring/query"
)
const (
libraryVersion = "0.1"
defaultBaseURL = "https://metrics-api.librato.com/v1/"
userAgent = "go-librato/" + libraryVersion
defaultMediaType = "application/json"
)
// A Client manages communication with the Librato API.
type Client struct {
// HTTP client used to communicate with the API
client *http.Client
// Headers to attach to every request made with the client. Headers will be
// used to provide Librato API authentication details and other necessary
// headers.
Headers map[string]string
// Email and Token contains the authentication details needed to authenticate
// against the Librato API.
Email, Token string
// Base URL for API requests. Defaults to the public Librato API, but can be
// set to an alternate endpoint if necessary. BaseURL should always be
// terminated by a slash.
BaseURL *url.URL
// User agent used when communicating with the Librato API.
UserAgent string
// Services used to manipulate API entities.
Spaces *SpacesService
Metrics *MetricsService
Alerts *AlertsService
Services *ServicesService
}
// NewClient returns a new Librato API client bound to the public Librato API.
func NewClient(email, token string) *Client {
bu, err := url.Parse(defaultBaseURL)
if err != nil {
panic("Default Librato API base URL couldn't be parsed")
}
return NewClientWithBaseURL(bu, email, token)
}
// NewClientWithBaseURL returned a new Librato API client with a custom base URL.
func NewClientWithBaseURL(baseURL *url.URL, email, token string) *Client {
headers := map[string]string{
"Content-Type": defaultMediaType,
"Accept": defaultMediaType,
}
c := &Client{
client: http.DefaultClient,
Headers: headers,
Email: email,
Token: token,
BaseURL: baseURL,
UserAgent: userAgent,
}
c.Spaces = &SpacesService{client: c}
c.Metrics = &MetricsService{client: c}
c.Alerts = &AlertsService{client: c}
c.Services = &ServicesService{client: c}
return c
}
// NewRequest creates an API request. A relative URL can be provided in urlStr,
// in which case it is resolved relative to the BaseURL of the Client.
// Relative URLs should always be specified without a preceding slash. If
// specified, the value pointed to by body is JSON encoded and included as the
// request body. If specified, the map provided by headers will be used to
// update request headers.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
rel, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
u := c.BaseURL.ResolveReference(rel)
var buf io.ReadWriter
if body != nil {
buf = new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
req.SetBasicAuth(c.Email, c.Token)
if c.UserAgent != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
for k, v := range c.Headers {
req.Header.Set(k, v)
}
return req, nil
}
// Do sends an API request and returns the API response. The API response is
// JSON decoded and stored in the value pointed to by v, or returned as an
// error if an API error has occurred. If v implements the io.Writer
// interface, the raw response body will be written to v, without attempting to
// first decode it.
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
err = CheckResponse(resp)
if err != nil {
return resp, err
}
if v != nil {
if w, ok := v.(io.Writer); ok {
_, err = io.Copy(w, resp.Body)
} else {
err = json.NewDecoder(resp.Body).Decode(v)
}
}
return resp, err
}
// ErrorResponse reports an error caused by an API request.
// ErrorResponse implements the Error interface.
type ErrorResponse struct {
// HTTP response that caused this error
Response *http.Response
// Error messages produces by Librato API.
Errors ErrorResponseMessages `json:"errors"`
}
func (er *ErrorResponse) Error() string {
buf := new(bytes.Buffer)
if er.Errors.Params != nil && len(er.Errors.Params) > 0 {
buf.WriteString(" Parameter errors:")
for param, errs := range er.Errors.Params {
fmt.Fprintf(buf, " %s:", param)
for _, err := range errs {
fmt.Fprintf(buf, " %s,", err)
}
}
buf.WriteString(".")
}
if er.Errors.Request != nil && len(er.Errors.Request) > 0 {
buf.WriteString(" Request errors:")
for _, err := range er.Errors.Request {
fmt.Fprintf(buf, " %s,", err)
}
buf.WriteString(".")
}
if er.Errors.System != nil && len(er.Errors.System) > 0 {
buf.WriteString(" System errors:")
for _, err := range er.Errors.System {
fmt.Fprintf(buf, " %s,", err)
}
buf.WriteString(".")
}
return fmt.Sprintf(
"%v %v: %d %v",
er.Response.Request.Method,
er.Response.Request.URL,
er.Response.StatusCode,
buf.String(),
)
}
// ErrorResponseMessages contains error messages returned from the Librato API.
type ErrorResponseMessages struct {
Params map[string][]string `json:"params,omitempty"`
Request []string `json:"request,omitempty"`
System []string `json:"system,omitempty"`
}
// CheckResponse checks the API response for errors; and returns them if
// present. A Response is considered an error if it has a status code outside
// the 2XX range.
func CheckResponse(r *http.Response) error {
if c := r.StatusCode; 200 <= c && c <= 299 {
return nil
}
errorResponse := &ErrorResponse{Response: r}
data, err := ioutil.ReadAll(r.Body)
if err == nil && data != nil {
json.Unmarshal(data, errorResponse)
}
return errorResponse
}
func urlWithOptions(s string, opt interface{}) (string, error) {
rv := reflect.ValueOf(opt)
if rv.Kind() == reflect.Ptr && rv.IsNil() {
return s, nil
}
u, err := url.Parse(s)
if err != nil {
return s, err
}
qs, err := query.Values(opt)
if err != nil {
return "", err
}
u.RawQuery = qs.Encode()
return u.String(), nil
}
// Bool is a helper routine that allocates a new bool value
// to store v and returns a pointer to it.
func Bool(v bool) *bool {
p := new(bool)
*p = v
return p
}
// Int is a helper routine that allocates a new int32 value
// to store v and returns a pointer to it, but unlike Int32
// its argument value is an int.
func Int(v int) *int {
p := new(int)
*p = v
return p
}
// Uint is a helper routine that allocates a new uint value
// to store v and returns a pointer to it.
func Uint(v uint) *uint {
p := new(uint)
*p = v
return p
}
// String is a helper routine that allocates a new string value
// to store v and returns a pointer to it.
func String(v string) *string {
p := new(string)
*p = v
return p
}
// Float is a helper routine that allocates a new float64 value
// to store v and returns a pointer to it.
func Float(v float64) *float64 {
p := new(float64)
*p = v
return p
}