X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/d81ea65da05119d5c6480d373b5d42bbee8ae1ad..0e57453d2b637a3d105d4e3d67031f3915f9d302:/sdk/go/arvadosclient/arvadosclient.go diff --git a/sdk/go/arvadosclient/arvadosclient.go b/sdk/go/arvadosclient/arvadosclient.go index 3d5aff7b23..99b08186a5 100644 --- a/sdk/go/arvadosclient/arvadosclient.go +++ b/sdk/go/arvadosclient/arvadosclient.go @@ -12,15 +12,41 @@ import ( "net/http" "net/url" "os" + "regexp" + "strings" ) // Errors var MissingArvadosApiHost = errors.New("Missing required environment variable ARVADOS_API_HOST") var MissingArvadosApiToken = errors.New("Missing required environment variable ARVADOS_API_TOKEN") -var ArvadosErrorForbidden = errors.New("Forbidden") -var ArvadosErrorNotFound = errors.New("Not found") -var ArvadosErrorBadRequest = errors.New("Bad request") -var ArvadosErrorServerError = errors.New("Server error") + +// Indicates an error that was returned by the API server. +type APIServerError struct { + // Address of server returning error, of the form "host:port". + ServerAddress string + + // Components of server response. + HttpStatusCode int + HttpStatusMessage string + + // Additional error details from response body. + ErrorDetails []string +} + +func (e APIServerError) Error() string { + if len(e.ErrorDetails) > 0 { + return fmt.Sprintf("arvados API server error: %s (%d: %s) returned by %s", + strings.Join(e.ErrorDetails, "; "), + e.HttpStatusCode, + e.HttpStatusMessage, + e.ServerAddress) + } else { + return fmt.Sprintf("arvados API server error: %d: %s returned by %s", + e.HttpStatusCode, + e.HttpStatusMessage, + e.ServerAddress) + } +} // Helper type so we don't have to write out 'map[string]interface{}' every time. type Dict map[string]interface{} @@ -42,16 +68,20 @@ type ArvadosClient struct { // If true, sets the X-External-Client header to indicate // the client is outside the cluster. External bool + + // Discovery document + DiscoveryDoc Dict } -// Create a new KeepClient, initialized with standard Arvados environment +// Create a new ArvadosClient, initialized with standard Arvados environment // variables ARVADOS_API_HOST, ARVADOS_API_TOKEN, and (optionally) // ARVADOS_API_HOST_INSECURE. -func MakeArvadosClient() (kc ArvadosClient, err error) { - insecure := (os.Getenv("ARVADOS_API_HOST_INSECURE") == "true") - external := (os.Getenv("ARVADOS_EXTERNAL_CLIENT") == "true") +func MakeArvadosClient() (ac ArvadosClient, err error) { + var matchTrue = regexp.MustCompile("^(?i:1|yes|true)$") + insecure := matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE")) + external := matchTrue.MatchString(os.Getenv("ARVADOS_EXTERNAL_CLIENT")) - kc = ArvadosClient{ + ac = ArvadosClient{ ApiServer: os.Getenv("ARVADOS_API_HOST"), ApiToken: os.Getenv("ARVADOS_API_TOKEN"), ApiInsecure: insecure, @@ -59,14 +89,14 @@ func MakeArvadosClient() (kc ArvadosClient, err error) { TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}}}, External: external} - if os.Getenv("ARVADOS_API_HOST") == "" { - return kc, MissingArvadosApiHost + if ac.ApiServer == "" { + return ac, MissingArvadosApiHost } - if os.Getenv("ARVADOS_API_TOKEN") == "" { - return kc, MissingArvadosApiToken + if ac.ApiToken == "" { + return ac, MissingArvadosApiToken } - return kc, err + return ac, err } // Low-level access to a resource. @@ -87,7 +117,9 @@ func (this ArvadosClient) CallRaw(method string, resource string, uuid string, a Scheme: "https", Host: this.ApiServer} - u.Path = "/arvados/v1" + if resource != API_DISCOVERY_RESOURCE { + u.Path = "/arvados/v1" + } if resource != "" { u.Path = u.Path + "/" + resource @@ -137,23 +169,41 @@ func (this ArvadosClient) CallRaw(method string, resource string, uuid string, a return nil, err } - switch resp.StatusCode { - case http.StatusOK: + if resp.StatusCode == http.StatusOK { return resp.Body, nil - case http.StatusForbidden: - resp.Body.Close() - return nil, ArvadosErrorForbidden - case http.StatusNotFound: - resp.Body.Close() - return nil, ArvadosErrorNotFound - default: - resp.Body.Close() - if resp.StatusCode >= 400 && resp.StatusCode <= 499 { - return nil, ArvadosErrorBadRequest - } else { - return nil, ArvadosErrorServerError + } + + defer resp.Body.Close() + return nil, newAPIServerError(this.ApiServer, resp) +} + +func newAPIServerError(ServerAddress string, resp *http.Response) APIServerError { + + ase := APIServerError{ + ServerAddress: ServerAddress, + HttpStatusCode: resp.StatusCode, + HttpStatusMessage: resp.Status} + + // If the response body has {"errors":["reason1","reason2"]} + // then return those reasons. + var errInfo = Dict{} + if err := json.NewDecoder(resp.Body).Decode(&errInfo); err == nil { + if errorList, ok := errInfo["errors"]; ok { + if errArray, ok := errorList.([]interface{}); ok { + for _, errItem := range errArray { + // We expect an array of strings here. + // Non-strings will be passed along + // JSON-encoded. + if s, ok := errItem.(string); ok { + ase.ErrorDetails = append(ase.ErrorDetails, s) + } else if j, err := json.Marshal(errItem); err == nil { + ase.ErrorDetails = append(ase.ErrorDetails, string(j)) + } + } + } } } + return ase } // Access to a resource. @@ -230,3 +280,29 @@ func (this ArvadosClient) Update(resource string, uuid string, parameters Dict, func (this ArvadosClient) List(resource string, parameters Dict, output interface{}) (err error) { return this.Call("GET", resource, "", "", parameters, output) } + +// API Discovery +// +// parameter - name of parameter to be discovered +// return +// value - value of the discovered parameter +// err - error accessing the resource, or nil if no error +var API_DISCOVERY_RESOURCE string = "discovery/v1/apis/arvados/v1/rest" + +func (this *ArvadosClient) Discovery(parameter string) (value interface{}, err error) { + if len(this.DiscoveryDoc) == 0 { + this.DiscoveryDoc = make(Dict) + err = this.Call("GET", API_DISCOVERY_RESOURCE, "", "", nil, &this.DiscoveryDoc) + if err != nil { + return nil, err + } + } + + var found bool + value, found = this.DiscoveryDoc[parameter] + if found { + return value, nil + } else { + return value, errors.New("Not found") + } +}