2018-05-14 16:26:38 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
2019-12-07 18:54:03 +00:00
|
|
|
"bytes"
|
2019-12-07 00:45:49 +00:00
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2018-05-14 16:26:38 +00:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2019-12-07 00:45:49 +00:00
|
|
|
"path"
|
2019-04-11 02:28:21 +00:00
|
|
|
|
2019-12-07 18:54:03 +00:00
|
|
|
"github.com/influxdata/influxdb"
|
2019-04-11 02:28:21 +00:00
|
|
|
"github.com/influxdata/influxdb/kit/tracing"
|
2018-05-14 16:26:38 +00:00
|
|
|
)
|
|
|
|
|
2018-09-17 02:39:46 +00:00
|
|
|
// Service connects to an InfluxDB via HTTP.
|
|
|
|
type Service struct {
|
|
|
|
Addr string
|
|
|
|
Token string
|
|
|
|
InsecureSkipVerify bool
|
|
|
|
|
|
|
|
*AuthorizationService
|
2019-12-07 18:54:03 +00:00
|
|
|
*BucketService
|
|
|
|
*DashboardService
|
2018-09-17 02:39:46 +00:00
|
|
|
*OrganizationService
|
|
|
|
*UserService
|
2019-02-14 20:32:54 +00:00
|
|
|
*VariableService
|
2019-12-07 18:54:03 +00:00
|
|
|
*WriteService
|
2018-09-17 02:39:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewService returns a service that is an HTTP
|
|
|
|
// client to a remote
|
2019-12-07 18:54:03 +00:00
|
|
|
func NewService(addr, token string) (*Service, error) {
|
|
|
|
httpClient, err := NewHTTPClient(addr, token, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-09-17 02:39:46 +00:00
|
|
|
return &Service{
|
|
|
|
Addr: addr,
|
|
|
|
Token: token,
|
|
|
|
AuthorizationService: &AuthorizationService{
|
|
|
|
Addr: addr,
|
|
|
|
Token: token,
|
|
|
|
},
|
2019-12-07 18:54:03 +00:00
|
|
|
BucketService: &BucketService{Client: httpClient},
|
|
|
|
DashboardService: &DashboardService{Client: httpClient},
|
2018-09-17 02:39:46 +00:00
|
|
|
OrganizationService: &OrganizationService{
|
|
|
|
Addr: addr,
|
|
|
|
Token: token,
|
|
|
|
},
|
|
|
|
UserService: &UserService{
|
|
|
|
Addr: addr,
|
|
|
|
Token: token,
|
|
|
|
},
|
2019-12-07 18:54:03 +00:00
|
|
|
VariableService: &VariableService{Client: httpClient},
|
|
|
|
WriteService: &WriteService{
|
2018-09-17 02:39:46 +00:00
|
|
|
Addr: addr,
|
|
|
|
Token: token,
|
|
|
|
},
|
2019-12-07 18:54:03 +00:00
|
|
|
}, nil
|
2018-09-17 02:39:46 +00:00
|
|
|
}
|
|
|
|
|
2019-06-26 05:48:15 +00:00
|
|
|
// NewURL concats addr and path.
|
2019-05-09 17:41:14 +00:00
|
|
|
func NewURL(addr, path string) (*url.URL, error) {
|
2018-05-14 16:26:38 +00:00
|
|
|
u, err := url.Parse(addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
u.Path = path
|
|
|
|
return u, nil
|
|
|
|
}
|
|
|
|
|
2019-06-26 05:48:15 +00:00
|
|
|
// NewClient returns an http.Client that pools connections and injects a span.
|
2019-05-09 17:41:14 +00:00
|
|
|
func NewClient(scheme string, insecure bool) *traceClient {
|
2018-08-15 20:14:51 +00:00
|
|
|
hc := &traceClient{
|
|
|
|
Client: http.Client{
|
|
|
|
Transport: defaultTransport,
|
|
|
|
},
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
if scheme == "https" && insecure {
|
|
|
|
hc.Transport = skipVerifyTransport
|
|
|
|
}
|
|
|
|
|
|
|
|
return hc
|
|
|
|
}
|
2018-08-15 20:14:51 +00:00
|
|
|
|
|
|
|
// traceClient always injects any opentracing trace into the client requests.
|
|
|
|
type traceClient struct {
|
|
|
|
http.Client
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do injects the trace and then performs the request.
|
|
|
|
func (c *traceClient) Do(r *http.Request) (*http.Response, error) {
|
2019-04-11 02:28:21 +00:00
|
|
|
span, _ := tracing.StartSpanFromContext(r.Context())
|
2019-06-26 05:48:15 +00:00
|
|
|
defer span.Finish()
|
2019-04-11 02:28:21 +00:00
|
|
|
tracing.InjectToHTTPRequest(span, r)
|
2018-08-15 20:14:51 +00:00
|
|
|
return c.Client.Do(r)
|
|
|
|
}
|
2019-12-07 00:45:49 +00:00
|
|
|
|
|
|
|
// HTTPClient is a basic http client that can make cReqs with out having to juggle
|
|
|
|
// the token and so forth. It provides sane defaults for checking response
|
|
|
|
// statuses, sets auth token when provided, and sets the content type to
|
|
|
|
// application/json for each request. The token, response checker, and
|
|
|
|
// content type can be overidden on the cReq as well.
|
|
|
|
type HTTPClient struct {
|
|
|
|
addr url.URL
|
|
|
|
token string
|
|
|
|
client *traceClient
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewHTTPClient creates a new HTTPClient(client).
|
|
|
|
func NewHTTPClient(addr, token string, insecureSkipVerify bool) (*HTTPClient, error) {
|
|
|
|
u, err := url.Parse(addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &HTTPClient{
|
|
|
|
addr: *u,
|
|
|
|
token: token,
|
|
|
|
client: NewClient(u.Scheme, insecureSkipVerify),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *HTTPClient) delete(urlPath string) *cReq {
|
2019-12-07 18:54:03 +00:00
|
|
|
return c.newClientReq(http.MethodDelete, urlPath, bodyEmpty())
|
2019-12-07 00:45:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *HTTPClient) get(urlPath string) *cReq {
|
2019-12-07 18:54:03 +00:00
|
|
|
return c.newClientReq(http.MethodGet, urlPath, bodyEmpty())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *HTTPClient) patch(urlPath string, bFn bodyFn) *cReq {
|
|
|
|
return c.newClientReq(http.MethodPatch, urlPath, bFn)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *HTTPClient) post(urlPath string, bFn bodyFn) *cReq {
|
|
|
|
return c.newClientReq(http.MethodPost, urlPath, bFn)
|
2019-12-07 00:45:49 +00:00
|
|
|
}
|
|
|
|
|
2019-12-07 18:54:03 +00:00
|
|
|
func (c *HTTPClient) put(urlPath string, bFn bodyFn) *cReq {
|
|
|
|
return c.newClientReq(http.MethodPut, urlPath, bFn)
|
2019-12-07 00:45:49 +00:00
|
|
|
}
|
|
|
|
|
2019-12-07 18:54:03 +00:00
|
|
|
type bodyFn func() (io.Reader, error)
|
|
|
|
|
|
|
|
func bodyEmpty() bodyFn {
|
|
|
|
return func() (io.Reader, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(@jsteenb2): discussion add a inspection for an OK() or Valid() method, then enforce
|
|
|
|
// that across all consumers?
|
|
|
|
func bodyJSON(v interface{}) bodyFn {
|
|
|
|
return func() (io.Reader, error) {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
if err := json.NewEncoder(&buf).Encode(v); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &buf, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *HTTPClient) newClientReq(method, urlPath string, bFn bodyFn) *cReq {
|
|
|
|
body, err := bFn()
|
|
|
|
if err != nil {
|
|
|
|
return &cReq{err: err}
|
|
|
|
}
|
|
|
|
|
2019-12-07 00:45:49 +00:00
|
|
|
u := c.addr
|
|
|
|
u.Path = path.Join(u.Path, urlPath)
|
|
|
|
req, err := http.NewRequest(method, u.String(), body)
|
|
|
|
if err != nil {
|
|
|
|
return &cReq{err: err}
|
|
|
|
}
|
|
|
|
if c.token != "" {
|
|
|
|
SetToken(c.token, req)
|
|
|
|
}
|
|
|
|
|
|
|
|
cr := &cReq{
|
2019-12-07 18:54:03 +00:00
|
|
|
client: c.client,
|
|
|
|
req: req,
|
|
|
|
statusFn: CheckError,
|
2019-12-07 00:45:49 +00:00
|
|
|
}
|
|
|
|
return cr.ContentType("application/json")
|
|
|
|
}
|
|
|
|
|
|
|
|
type cReq struct {
|
|
|
|
client interface {
|
|
|
|
Do(*http.Request) (*http.Response, error)
|
|
|
|
}
|
2019-12-07 18:54:03 +00:00
|
|
|
req *http.Request
|
|
|
|
decodeFn func(*http.Response) error
|
|
|
|
respFn func(*http.Response) error
|
|
|
|
statusFn func(*http.Response) error
|
2019-12-07 00:45:49 +00:00
|
|
|
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *cReq) Header(k, v string) *cReq {
|
|
|
|
if r.err != nil {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
r.req.Header.Add(k, v)
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2019-12-07 18:54:03 +00:00
|
|
|
func (r *cReq) Queries(pairs ...[2]string) *cReq {
|
2019-12-07 00:45:49 +00:00
|
|
|
if r.err != nil || len(pairs) == 0 {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
params := r.req.URL.Query()
|
|
|
|
for _, p := range pairs {
|
2019-12-07 18:54:03 +00:00
|
|
|
params.Add(p[0], p[1])
|
2019-12-07 00:45:49 +00:00
|
|
|
}
|
|
|
|
r.req.URL.RawQuery = params.Encode()
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *cReq) ContentType(ct string) *cReq {
|
|
|
|
return r.Header("Content-Type", ct)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *cReq) DecodeJSON(v interface{}) *cReq {
|
2019-12-07 18:54:03 +00:00
|
|
|
r.decodeFn = func(resp *http.Response) error {
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(v); err != nil {
|
|
|
|
return &influxdb.Error{
|
|
|
|
Code: influxdb.EInvalid,
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return r
|
2019-12-07 00:45:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *cReq) RespFn(fn func(*http.Response) error) *cReq {
|
|
|
|
r.respFn = fn
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2019-12-07 18:54:03 +00:00
|
|
|
func (r *cReq) StatusFn(fn func(*http.Response) error) *cReq {
|
|
|
|
r.statusFn = fn
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2019-12-07 00:45:49 +00:00
|
|
|
func (r *cReq) Do(ctx context.Context) error {
|
|
|
|
if r.err != nil {
|
|
|
|
return r.err
|
|
|
|
}
|
|
|
|
r.req = r.req.WithContext(ctx)
|
|
|
|
|
|
|
|
resp, err := r.client.Do(r.req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
io.Copy(ioutil.Discard, resp.Body) // drain body completely
|
|
|
|
resp.Body.Close()
|
|
|
|
}()
|
|
|
|
|
2019-12-07 18:54:03 +00:00
|
|
|
responseFns := []func(*http.Response) error{
|
|
|
|
r.statusFn,
|
|
|
|
r.decodeFn,
|
|
|
|
r.respFn,
|
|
|
|
}
|
|
|
|
for _, fn := range responseFns {
|
|
|
|
if fn != nil {
|
|
|
|
if err := fn(resp); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2019-12-07 00:45:49 +00:00
|
|
|
}
|