202 lines
4.5 KiB
Go
202 lines
4.5 KiB
Go
// Package api provides methods for interacting with the Circonus API
|
|
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-retryablehttp"
|
|
)
|
|
|
|
const (
|
|
// a few sensible defaults
|
|
defaultAPIURL = "https://api.circonus.com/v2"
|
|
defaultAPIApp = "circonus-gometrics"
|
|
minRetryWait = 10 * time.Millisecond
|
|
maxRetryWait = 50 * time.Millisecond
|
|
maxRetries = 3
|
|
)
|
|
|
|
// TokenKeyType - Circonus API Token key
|
|
type TokenKeyType string
|
|
|
|
// TokenAppType - Circonus API Token app name
|
|
type TokenAppType string
|
|
|
|
// IDType Circonus object id (numeric portion of cid)
|
|
type IDType int
|
|
|
|
// CIDType Circonus object cid
|
|
type CIDType string
|
|
|
|
// URLType submission url type
|
|
type URLType string
|
|
|
|
// SearchQueryType search query
|
|
type SearchQueryType string
|
|
|
|
// SearchTagType search/select tag type
|
|
type SearchTagType string
|
|
|
|
// Config options for Circonus API
|
|
type Config struct {
|
|
URL string
|
|
TokenKey string
|
|
TokenApp string
|
|
Log *log.Logger
|
|
Debug bool
|
|
}
|
|
|
|
// API Circonus API
|
|
type API struct {
|
|
apiURL *url.URL
|
|
key TokenKeyType
|
|
app TokenAppType
|
|
Debug bool
|
|
Log *log.Logger
|
|
}
|
|
|
|
// NewAPI returns a new Circonus API
|
|
func NewAPI(ac *Config) (*API, error) {
|
|
|
|
if ac == nil {
|
|
return nil, errors.New("Invalid API configuration (nil)")
|
|
}
|
|
|
|
key := TokenKeyType(ac.TokenKey)
|
|
if key == "" {
|
|
return nil, errors.New("API Token is required")
|
|
}
|
|
|
|
app := TokenAppType(ac.TokenApp)
|
|
if app == "" {
|
|
app = defaultAPIApp
|
|
}
|
|
|
|
au := string(ac.URL)
|
|
if au == "" {
|
|
au = defaultAPIURL
|
|
}
|
|
if !strings.Contains(au, "/") {
|
|
// if just a hostname is passed, ASSume "https" and a path prefix of "/v2"
|
|
au = fmt.Sprintf("https://%s/v2", ac.URL)
|
|
}
|
|
if last := len(au) - 1; last >= 0 && au[last] == '/' {
|
|
au = au[:last]
|
|
}
|
|
apiURL, err := url.Parse(au)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
a := &API{apiURL, key, app, ac.Debug, ac.Log}
|
|
|
|
if a.Log == nil {
|
|
if a.Debug {
|
|
a.Log = log.New(os.Stderr, "", log.LstdFlags)
|
|
} else {
|
|
a.Log = log.New(ioutil.Discard, "", log.LstdFlags)
|
|
}
|
|
}
|
|
|
|
return a, nil
|
|
}
|
|
|
|
// Get API request
|
|
func (a *API) Get(reqPath string) ([]byte, error) {
|
|
return a.apiCall("GET", reqPath, nil)
|
|
}
|
|
|
|
// Delete API request
|
|
func (a *API) Delete(reqPath string) ([]byte, error) {
|
|
return a.apiCall("DELETE", reqPath, nil)
|
|
}
|
|
|
|
// Post API request
|
|
func (a *API) Post(reqPath string, data []byte) ([]byte, error) {
|
|
return a.apiCall("POST", reqPath, data)
|
|
}
|
|
|
|
// Put API request
|
|
func (a *API) Put(reqPath string, data []byte) ([]byte, error) {
|
|
return a.apiCall("PUT", reqPath, data)
|
|
}
|
|
|
|
// apiCall call Circonus API
|
|
func (a *API) apiCall(reqMethod string, reqPath string, data []byte) ([]byte, error) {
|
|
dataReader := bytes.NewReader(data)
|
|
reqURL := a.apiURL.String()
|
|
|
|
if reqPath[:1] != "/" {
|
|
reqURL += "/"
|
|
}
|
|
if reqPath[:3] == "/v2" {
|
|
reqURL += reqPath[3:len(reqPath)]
|
|
} else {
|
|
reqURL += reqPath
|
|
}
|
|
|
|
req, err := retryablehttp.NewRequest(reqMethod, reqURL, dataReader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("[ERROR] creating API request: %s %+v", reqURL, err)
|
|
}
|
|
req.Header.Add("Accept", "application/json")
|
|
req.Header.Add("X-Circonus-Auth-Token", string(a.key))
|
|
req.Header.Add("X-Circonus-App-Name", string(a.app))
|
|
|
|
client := retryablehttp.NewClient()
|
|
client.RetryWaitMin = minRetryWait
|
|
client.RetryWaitMax = maxRetryWait
|
|
client.RetryMax = maxRetries
|
|
client.Logger = a.Log
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
stdClient := &http.Client{}
|
|
dataReader.Seek(0, 0)
|
|
stdRequest, _ := http.NewRequest(reqMethod, reqURL, dataReader)
|
|
stdRequest.Header.Add("Accept", "application/json")
|
|
stdRequest.Header.Add("X-Circonus-Auth-Token", string(a.key))
|
|
stdRequest.Header.Add("X-Circonus-App-Name", string(a.app))
|
|
res, errSC := stdClient.Do(stdRequest)
|
|
if errSC != nil {
|
|
return nil, fmt.Errorf("[ERROR] fetching %s: %s", reqURL, errSC)
|
|
}
|
|
|
|
if res != nil && res.Body != nil {
|
|
defer res.Body.Close()
|
|
body, _ := ioutil.ReadAll(res.Body)
|
|
if a.Debug {
|
|
a.Log.Printf("[DEBUG] %v\n", string(body))
|
|
}
|
|
return nil, fmt.Errorf("[ERROR] %s", string(body))
|
|
}
|
|
|
|
return nil, fmt.Errorf("[ERROR] fetching %s: %s", reqURL, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("[ERROR] reading body %+v", err)
|
|
}
|
|
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
msg := fmt.Sprintf("API response code %d: %s", resp.StatusCode, string(body))
|
|
if a.Debug {
|
|
a.Log.Printf("[DEBUG] %s\n", msg)
|
|
}
|
|
|
|
return nil, fmt.Errorf("[ERROR] %s", msg)
|
|
}
|
|
|
|
return body, nil
|
|
}
|