X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/9a44fb9de57a2f44347aecd26928eee03ea3c60c..b3fa9983ac0b7b38a5b3787af56a7bb1502ae3be:/sdk/go/arvados/client.go diff --git a/sdk/go/arvados/client.go b/sdk/go/arvados/client.go index d6d610da91..9691e7a07e 100644 --- a/sdk/go/arvados/client.go +++ b/sdk/go/arvados/client.go @@ -10,6 +10,9 @@ import ( "net/http" "net/url" "os" + "regexp" + "strings" + "time" ) // A Client is an HTTP client with an API endpoint and a set of @@ -20,8 +23,8 @@ import ( // of results using List APIs. type Client struct { // HTTP client used to make requests. If nil, - // http.DefaultClient or InsecureHTTPClient will be used. - Client *http.Client + // DefaultSecureClient or InsecureHTTPClient will be used. + Client *http.Client `json:"-"` // Hostname (or host:port) of Arvados API server. APIHost string @@ -32,6 +35,15 @@ type Client struct { // Accept unverified certificates. This works only if the // Client field is nil: otherwise, it has no effect. Insecure bool + + // Override keep service discovery with a list of base + // URIs. (Currently there are no Client methods for + // discovering keep services so this is just a convenience for + // callers who use a Client to initialize an + // arvadosclient.ArvadosClient.) + KeepServiceURIs []string `json:",omitempty"` + + dd *DiscoveryDocument } // The default http.Client used by a Client with Insecure==true and @@ -39,16 +51,26 @@ type Client struct { var InsecureHTTPClient = &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true}}} + InsecureSkipVerify: true}}, + Timeout: 5 * time.Minute} + +// The default http.Client used by a Client otherwise. +var DefaultSecureClient = &http.Client{ + Timeout: 5 * time.Minute} // NewClientFromEnv creates a new Client that uses the default HTTP // client with the API endpoint and credentials given by the // ARVADOS_API_* environment variables. func NewClientFromEnv() *Client { + var svcs []string + if s := os.Getenv("ARVADOS_KEEP_SERVICES"); s != "" { + svcs = strings.Split(s, " ") + } return &Client{ - APIHost: os.Getenv("ARVADOS_API_HOST"), - AuthToken: os.Getenv("ARVADOS_API_TOKEN"), - Insecure: os.Getenv("ARVADOS_API_HOST_INSECURE") != "", + APIHost: os.Getenv("ARVADOS_API_HOST"), + AuthToken: os.Getenv("ARVADOS_API_TOKEN"), + Insecure: os.Getenv("ARVADOS_API_HOST_INSECURE") != "", + KeepServiceURIs: svcs, } } @@ -159,6 +181,7 @@ func (c *Client) RequestAndDecode(dst interface{}, method, path string, body io. if err != nil { return err } + req.Header.Set("Content-type", "application/x-www-form-urlencoded") return c.DoAndDecode(dst, req) } @@ -169,7 +192,7 @@ func (c *Client) httpClient() *http.Client { case c.Insecure: return InsecureHTTPClient default: - return http.DefaultClient + return DefaultSecureClient } } @@ -179,14 +202,108 @@ func (c *Client) apiURL(path string) string { // DiscoveryDocument is the Arvados server's description of itself. type DiscoveryDocument struct { - DefaultCollectionReplication int `json:"defaultCollectionReplication"` - BlobSignatureTTL int64 `json:"blobSignatureTtl"` + BasePath string `json:"basePath"` + DefaultCollectionReplication int `json:"defaultCollectionReplication"` + BlobSignatureTTL int64 `json:"blobSignatureTtl"` + Schemas map[string]Schema `json:"schemas"` + Resources map[string]Resource `json:"resources"` +} + +type Resource struct { + Methods map[string]ResourceMethod `json:"methods"` +} + +type ResourceMethod struct { + HTTPMethod string `json:"httpMethod"` + Path string `json:"path"` + Response MethodResponse `json:"response"` +} + +type MethodResponse struct { + Ref string `json:"$ref"` +} + +type Schema struct { + UUIDPrefix string `json:"uuidPrefix"` } // DiscoveryDocument returns a *DiscoveryDocument. The returned object // should not be modified: the same object may be returned by // subsequent calls. func (c *Client) DiscoveryDocument() (*DiscoveryDocument, error) { + if c.dd != nil { + return c.dd, nil + } var dd DiscoveryDocument - return &dd, c.RequestAndDecode(&dd, "GET", "discovery/v1/apis/arvados/v1/rest", nil, nil) + err := c.RequestAndDecode(&dd, "GET", "discovery/v1/apis/arvados/v1/rest", nil, nil) + if err != nil { + return nil, err + } + c.dd = &dd + return c.dd, nil +} + +var pdhRegexp = regexp.MustCompile(`^[0-9a-f]{32}\+\d+$`) + +func (c *Client) modelForUUID(dd *DiscoveryDocument, uuid string) (string, error) { + if pdhRegexp.MatchString(uuid) { + return "Collection", nil + } + if len(uuid) != 27 { + return "", fmt.Errorf("invalid UUID: %q", uuid) + } + infix := uuid[6:11] + var model string + for m, s := range dd.Schemas { + if s.UUIDPrefix == infix { + model = m + break + } + } + if model == "" { + return "", fmt.Errorf("unrecognized type portion %q in UUID %q", infix, uuid) + } + return model, nil +} + +func (c *Client) KindForUUID(uuid string) (string, error) { + dd, err := c.DiscoveryDocument() + if err != nil { + return "", err + } + model, err := c.modelForUUID(dd, uuid) + if err != nil { + return "", err + } + return "arvados#" + strings.ToLower(model[:1]) + model[1:], nil +} + +func (c *Client) PathForUUID(method, uuid string) (string, error) { + dd, err := c.DiscoveryDocument() + if err != nil { + return "", err + } + model, err := c.modelForUUID(dd, uuid) + if err != nil { + return "", err + } + var resource string + for r, rsc := range dd.Resources { + if rsc.Methods["get"].Response.Ref == model { + resource = r + break + } + } + if resource == "" { + return "", fmt.Errorf("no resource for model: %q", model) + } + m, ok := dd.Resources[resource].Methods[method] + if !ok { + return "", fmt.Errorf("no method %q for resource %q", method, resource) + } + path := dd.BasePath + strings.Replace(m.Path, "{uuid}", uuid, -1) + if path[0] == '/' { + path = path[1:] + } + return path, nil }