1 /* Simple Arvados Go SDK for communicating with API server. */
19 var MissingArvadosApiHost = errors.New("Missing required environment variable ARVADOS_API_HOST")
20 var MissingArvadosApiToken = errors.New("Missing required environment variable ARVADOS_API_TOKEN")
22 type ArvadosApiError struct {
28 func (e ArvadosApiError) Error() string { return e.error.Error() }
30 // Helper type so we don't have to write out 'map[string]interface{}' every time.
31 type Dict map[string]interface{}
33 // Information about how to contact the Arvados server
34 type ArvadosClient struct {
35 // Arvados API server, form "host:port"
38 // Arvados API token for authentication
41 // Whether to require a valid SSL certificate or not
44 // Client object shared by client requests. Supports HTTP KeepAlive.
47 // If true, sets the X-External-Client header to indicate
48 // the client is outside the cluster.
52 // Create a new KeepClient, initialized with standard Arvados environment
53 // variables ARVADOS_API_HOST, ARVADOS_API_TOKEN, and (optionally)
54 // ARVADOS_API_HOST_INSECURE.
55 func MakeArvadosClient() (kc ArvadosClient, err error) {
56 insecure := (os.Getenv("ARVADOS_API_HOST_INSECURE") == "true")
57 external := (os.Getenv("ARVADOS_EXTERNAL_CLIENT") == "true")
60 ApiServer: os.Getenv("ARVADOS_API_HOST"),
61 ApiToken: os.Getenv("ARVADOS_API_TOKEN"),
62 ApiInsecure: insecure,
63 Client: &http.Client{Transport: &http.Transport{
64 TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}}},
67 if os.Getenv("ARVADOS_API_HOST") == "" {
68 return kc, MissingArvadosApiHost
70 if os.Getenv("ARVADOS_API_TOKEN") == "" {
71 return kc, MissingArvadosApiToken
77 // Low-level access to a resource.
79 // method - HTTP method, one of GET, HEAD, PUT, POST or DELETE
80 // resource - the arvados resource to act on
81 // uuid - the uuid of the specific item to access (may be empty)
82 // action - sub-action to take on the resource or uuid (may be empty)
83 // parameters - method parameters
86 // reader - the body reader, or nil if there was an error
87 // err - error accessing the resource, or nil if no error
88 func (this ArvadosClient) CallRaw(method string, resource string, uuid string, action string, parameters Dict) (reader io.ReadCloser, err error) {
95 u.Path = "/arvados/v1"
98 u.Path = u.Path + "/" + resource
101 u.Path = u.Path + "/" + uuid
104 u.Path = u.Path + "/" + action
107 if parameters == nil {
108 parameters = make(Dict)
111 parameters["format"] = "json"
113 vals := make(url.Values)
114 for k, v := range parameters {
115 m, err := json.Marshal(v)
117 vals.Set(k, string(m))
121 if method == "GET" || method == "HEAD" {
122 u.RawQuery = vals.Encode()
123 if req, err = http.NewRequest(method, u.String(), nil); err != nil {
127 if req, err = http.NewRequest(method, u.String(), bytes.NewBufferString(vals.Encode())); err != nil {
130 req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
133 // Add api token header
134 req.Header.Add("Authorization", fmt.Sprintf("OAuth2 %s", this.ApiToken))
136 req.Header.Add("X-External-Client", "1")
140 var resp *http.Response
141 if resp, err = this.Client.Do(req); err != nil {
145 if resp.StatusCode == http.StatusOK {
146 return resp.Body, nil
149 defer resp.Body.Close()
150 errorText := fmt.Sprintf("API response: %s", resp.Status)
152 // If the response body has {"errors":["reason1","reason2"]}
153 // then return those reasons.
155 if err := json.NewDecoder(resp.Body).Decode(&errInfo); err == nil {
156 if errorList, ok := errInfo["errors"]; ok {
157 var errorStrings []string
158 if errArray, ok := errorList.([]interface{}); ok {
159 for _, errItem := range errArray {
160 // We expect an array of strings here.
161 // Non-strings will be passed along
163 if s, ok := errItem.(string); ok {
164 errorStrings = append(errorStrings, s)
165 } else if j, err := json.Marshal(errItem); err == nil {
166 errorStrings = append(errorStrings, string(j))
169 errorText = strings.Join(errorStrings, "; ")
173 return nil, ArvadosApiError{errors.New(errorText), resp.StatusCode, resp.Status}
176 // Access to a resource.
178 // method - HTTP method, one of GET, HEAD, PUT, POST or DELETE
179 // resource - the arvados resource to act on
180 // uuid - the uuid of the specific item to access (may be empty)
181 // action - sub-action to take on the resource or uuid (may be empty)
182 // parameters - method parameters
183 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
185 // err - error accessing the resource, or nil if no error
186 func (this ArvadosClient) Call(method string, resource string, uuid string, action string, parameters Dict, output interface{}) (err error) {
187 var reader io.ReadCloser
188 reader, err = this.CallRaw(method, resource, uuid, action, parameters)
197 dec := json.NewDecoder(reader)
198 if err = dec.Decode(output); err != nil {
205 // Create a new instance of a resource.
207 // resource - the arvados resource on which to create an item
208 // parameters - method parameters
209 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
211 // err - error accessing the resource, or nil if no error
212 func (this ArvadosClient) Create(resource string, parameters Dict, output interface{}) (err error) {
213 return this.Call("POST", resource, "", "", parameters, output)
216 // Delete an instance of a resource.
218 // resource - the arvados resource on which to delete an item
219 // uuid - the item to delete
220 // parameters - method parameters
221 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
223 // err - error accessing the resource, or nil if no error
224 func (this ArvadosClient) Delete(resource string, uuid string, parameters Dict, output interface{}) (err error) {
225 return this.Call("DELETE", resource, uuid, "", parameters, output)
228 // Update fields of an instance of a resource.
230 // resource - the arvados resource on which to update the item
231 // uuid - the item to update
232 // parameters - method parameters
233 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
235 // err - error accessing the resource, or nil if no error
236 func (this ArvadosClient) Update(resource string, uuid string, parameters Dict, output interface{}) (err error) {
237 return this.Call("PUT", resource, uuid, "", parameters, output)
240 // List the instances of a resource
242 // resource - the arvados resource on which to list
243 // parameters - method parameters
244 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
246 // err - error accessing the resource, or nil if no error
247 func (this ArvadosClient) List(resource string, parameters Dict, output interface{}) (err error) {
248 return this.Call("GET", resource, "", "", parameters, output)