15003: Update dispatchcloud to new config struct.
[arvados.git] / sdk / go / arvados / client.go
index 24f3faac16053fd6b40457a6111a7ac4d954f994..cbc2ca72f035f150fce46613fa015d299a9bbd7b 100644 (file)
@@ -6,6 +6,7 @@ package arvados
 
 import (
        "bytes"
+       "context"
        "crypto/tls"
        "encoding/json"
        "fmt"
@@ -19,6 +20,8 @@ import (
        "regexp"
        "strings"
        "time"
+
+       "git.curoverse.com/arvados.git/sdk/go/httpserver"
 )
 
 // A Client is an HTTP client with an API endpoint and a set of
@@ -50,6 +53,8 @@ type Client struct {
        KeepServiceURIs []string `json:",omitempty"`
 
        dd *DiscoveryDocument
+
+       ctx context.Context
 }
 
 // The default http.Client used by a Client with Insecure==true and
@@ -64,6 +69,21 @@ var InsecureHTTPClient = &http.Client{
 var DefaultSecureClient = &http.Client{
        Timeout: 5 * time.Minute}
 
+// NewClientFromConfig creates a new Client that uses the endpoints in
+// the given cluster.
+//
+// AuthToken is left empty for the caller to populate.
+func NewClientFromConfig(cluster *Cluster) (*Client, error) {
+       ctrlURL := cluster.Services.Controller.ExternalURL
+       if ctrlURL.Host == "" {
+               return nil, fmt.Errorf("no host in config Services.Controller.ExternalURL: %v", ctrlURL)
+       }
+       return &Client{
+               APIHost:  ctrlURL.Host,
+               Insecure: cluster.TLS.Insecure,
+       }, nil
+}
+
 // NewClientFromEnv creates a new Client that uses the default HTTP
 // client with the API endpoint and credentials given by the
 // ARVADOS_API_* environment variables.
@@ -92,11 +112,26 @@ func NewClientFromEnv() *Client {
        }
 }
 
-// Do adds authentication headers and then calls (*http.Client)Do().
+var reqIDGen = httpserver.IDGenerator{Prefix: "req-"}
+
+// Do adds Authorization and X-Request-Id headers and then calls
+// (*http.Client)Do().
 func (c *Client) Do(req *http.Request) (*http.Response, error) {
        if c.AuthToken != "" {
                req.Header.Add("Authorization", "OAuth2 "+c.AuthToken)
        }
+
+       if req.Header.Get("X-Request-Id") == "" {
+               reqid, _ := c.context().Value(contextKeyRequestID).(string)
+               if reqid == "" {
+                       reqid = reqIDGen.Next()
+               }
+               if req.Header == nil {
+                       req.Header = http.Header{"X-Request-Id": {reqid}}
+               } else {
+                       req.Header.Set("X-Request-Id", reqid)
+               }
+       }
        return c.httpClient().Do(req)
 }
 
@@ -190,14 +225,19 @@ func (c *Client) RequestAndDecode(dst interface{}, method, path string, body io.
        if err != nil {
                return err
        }
-       if (method == "GET" || body != nil) && urlValues != nil {
-               // FIXME: what if params don't fit in URL
+       if urlValues == nil {
+               // Nothing to send
+       } else if method == "GET" || method == "HEAD" || body != nil {
+               // Must send params in query part of URL (FIXME: what
+               // if resulting URL is too long?)
                u, err := url.Parse(urlString)
                if err != nil {
                        return err
                }
                u.RawQuery = urlValues.Encode()
                urlString = u.String()
+       } else {
+               body = strings.NewReader(urlValues.Encode())
        }
        req, err := http.NewRequest(method, urlString, body)
        if err != nil {
@@ -225,6 +265,23 @@ func (c *Client) UpdateBody(rsc resource) io.Reader {
        return bytes.NewBufferString(v.Encode())
 }
 
+type contextKey string
+
+var contextKeyRequestID contextKey = "X-Request-Id"
+
+func (c *Client) WithRequestID(reqid string) *Client {
+       cc := *c
+       cc.ctx = context.WithValue(cc.context(), contextKeyRequestID, reqid)
+       return &cc
+}
+
+func (c *Client) context() context.Context {
+       if c.ctx == nil {
+               return context.Background()
+       }
+       return c.ctx
+}
+
 func (c *Client) httpClient() *http.Client {
        switch {
        case c.Client != nil: