1 /* Simple Arvados Go SDK for communicating with API server. */
20 var MissingArvadosApiHost = errors.New("Missing required environment variable ARVADOS_API_HOST")
21 var MissingArvadosApiToken = errors.New("Missing required environment variable ARVADOS_API_TOKEN")
23 type ArvadosApiError struct {
29 func (e ArvadosApiError) Error() string { return e.error.Error() }
31 // Helper type so we don't have to write out 'map[string]interface{}' every time.
32 type Dict map[string]interface{}
34 // Information about how to contact the Arvados server
35 type ArvadosClient struct {
36 // Arvados API server, form "host:port"
39 // Arvados API token for authentication
42 // Whether to require a valid SSL certificate or not
45 // Client object shared by client requests. Supports HTTP KeepAlive.
48 // If true, sets the X-External-Client header to indicate
49 // the client is outside the cluster.
53 // Create a new KeepClient, initialized with standard Arvados environment
54 // variables ARVADOS_API_HOST, ARVADOS_API_TOKEN, and (optionally)
55 // ARVADOS_API_HOST_INSECURE.
56 func MakeArvadosClient() (kc ArvadosClient, err error) {
57 var matchTrue = regexp.MustCompile("^(?i:1|yes|true)$")
58 insecure := matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE"))
59 external := matchTrue.MatchString(os.Getenv("ARVADOS_EXTERNAL_CLIENT"))
62 ApiServer: os.Getenv("ARVADOS_API_HOST"),
63 ApiToken: os.Getenv("ARVADOS_API_TOKEN"),
64 ApiInsecure: insecure,
65 Client: &http.Client{Transport: &http.Transport{
66 TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}}},
69 if kc.ApiServer == "" {
70 return kc, MissingArvadosApiHost
72 if kc.ApiToken == "" {
73 return kc, MissingArvadosApiToken
79 // Low-level access to a resource.
81 // method - HTTP method, one of GET, HEAD, PUT, POST or DELETE
82 // resource - the arvados resource to act on
83 // uuid - the uuid of the specific item to access (may be empty)
84 // action - sub-action to take on the resource or uuid (may be empty)
85 // parameters - method parameters
88 // reader - the body reader, or nil if there was an error
89 // err - error accessing the resource, or nil if no error
90 func (this ArvadosClient) CallRaw(method string, resource string, uuid string, action string, parameters Dict) (reader io.ReadCloser, err error) {
97 u.Path = "/arvados/v1"
100 u.Path = u.Path + "/" + resource
103 u.Path = u.Path + "/" + uuid
106 u.Path = u.Path + "/" + action
109 if parameters == nil {
110 parameters = make(Dict)
113 parameters["format"] = "json"
115 vals := make(url.Values)
116 for k, v := range parameters {
117 m, err := json.Marshal(v)
119 vals.Set(k, string(m))
123 if method == "GET" || method == "HEAD" {
124 u.RawQuery = vals.Encode()
125 if req, err = http.NewRequest(method, u.String(), nil); err != nil {
129 if req, err = http.NewRequest(method, u.String(), bytes.NewBufferString(vals.Encode())); err != nil {
132 req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
135 // Add api token header
136 req.Header.Add("Authorization", fmt.Sprintf("OAuth2 %s", this.ApiToken))
138 req.Header.Add("X-External-Client", "1")
142 var resp *http.Response
143 if resp, err = this.Client.Do(req); err != nil {
147 if resp.StatusCode == http.StatusOK {
148 return resp.Body, nil
151 defer resp.Body.Close()
152 errorText := fmt.Sprintf("API response: %s", resp.Status)
154 // If the response body has {"errors":["reason1","reason2"]}
155 // then return those reasons.
157 if err := json.NewDecoder(resp.Body).Decode(&errInfo); err == nil {
158 if errorList, ok := errInfo["errors"]; ok {
159 var errorStrings []string
160 if errArray, ok := errorList.([]interface{}); ok {
161 for _, errItem := range errArray {
162 // We expect an array of strings here.
163 // Non-strings will be passed along
165 if s, ok := errItem.(string); ok {
166 errorStrings = append(errorStrings, s)
167 } else if j, err := json.Marshal(errItem); err == nil {
168 errorStrings = append(errorStrings, string(j))
171 errorText = strings.Join(errorStrings, "; ")
175 return nil, ArvadosApiError{errors.New(errorText), resp.StatusCode, resp.Status}
178 // Access to a resource.
180 // method - HTTP method, one of GET, HEAD, PUT, POST or DELETE
181 // resource - the arvados resource to act on
182 // uuid - the uuid of the specific item to access (may be empty)
183 // action - sub-action to take on the resource or uuid (may be empty)
184 // parameters - method parameters
185 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
187 // err - error accessing the resource, or nil if no error
188 func (this ArvadosClient) Call(method string, resource string, uuid string, action string, parameters Dict, output interface{}) (err error) {
189 var reader io.ReadCloser
190 reader, err = this.CallRaw(method, resource, uuid, action, parameters)
199 dec := json.NewDecoder(reader)
200 if err = dec.Decode(output); err != nil {
207 // Create a new instance of a resource.
209 // resource - the arvados resource on which to create an item
210 // parameters - method parameters
211 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
213 // err - error accessing the resource, or nil if no error
214 func (this ArvadosClient) Create(resource string, parameters Dict, output interface{}) (err error) {
215 return this.Call("POST", resource, "", "", parameters, output)
218 // Delete an instance of a resource.
220 // resource - the arvados resource on which to delete an item
221 // uuid - the item to delete
222 // parameters - method parameters
223 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
225 // err - error accessing the resource, or nil if no error
226 func (this ArvadosClient) Delete(resource string, uuid string, parameters Dict, output interface{}) (err error) {
227 return this.Call("DELETE", resource, uuid, "", parameters, output)
230 // Update fields of an instance of a resource.
232 // resource - the arvados resource on which to update the item
233 // uuid - the item to update
234 // parameters - method parameters
235 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
237 // err - error accessing the resource, or nil if no error
238 func (this ArvadosClient) Update(resource string, uuid string, parameters Dict, output interface{}) (err error) {
239 return this.Call("PUT", resource, uuid, "", parameters, output)
242 // List the instances of a resource
244 // resource - the arvados resource on which to list
245 // parameters - method parameters
246 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
248 // err - error accessing the resource, or nil if no error
249 func (this ArvadosClient) List(resource string, parameters Dict, output interface{}) (err error) {
250 return this.Call("GET", resource, "", "", parameters, output)