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 if resource != API_DISCOVERY_RESOURCE {
98 u.Path = "/arvados/v1"
102 u.Path = u.Path + "/" + resource
105 u.Path = u.Path + "/" + uuid
108 u.Path = u.Path + "/" + action
111 if parameters == nil {
112 parameters = make(Dict)
115 parameters["format"] = "json"
117 vals := make(url.Values)
118 for k, v := range parameters {
119 m, err := json.Marshal(v)
121 vals.Set(k, string(m))
125 if method == "GET" || method == "HEAD" {
126 u.RawQuery = vals.Encode()
127 if req, err = http.NewRequest(method, u.String(), nil); err != nil {
131 if req, err = http.NewRequest(method, u.String(), bytes.NewBufferString(vals.Encode())); err != nil {
134 req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
137 // Add api token header
138 req.Header.Add("Authorization", fmt.Sprintf("OAuth2 %s", this.ApiToken))
140 req.Header.Add("X-External-Client", "1")
144 var resp *http.Response
145 if resp, err = this.Client.Do(req); err != nil {
149 if resp.StatusCode == http.StatusOK {
150 return resp.Body, nil
153 defer resp.Body.Close()
154 errorText := fmt.Sprintf("API response: %s", resp.Status)
156 // If the response body has {"errors":["reason1","reason2"]}
157 // then return those reasons.
159 if err := json.NewDecoder(resp.Body).Decode(&errInfo); err == nil {
160 if errorList, ok := errInfo["errors"]; ok {
161 var errorStrings []string
162 if errArray, ok := errorList.([]interface{}); ok {
163 for _, errItem := range errArray {
164 // We expect an array of strings here.
165 // Non-strings will be passed along
167 if s, ok := errItem.(string); ok {
168 errorStrings = append(errorStrings, s)
169 } else if j, err := json.Marshal(errItem); err == nil {
170 errorStrings = append(errorStrings, string(j))
173 errorText = strings.Join(errorStrings, "; ")
177 return nil, ArvadosApiError{errors.New(errorText), resp.StatusCode, resp.Status}
180 // Access to a resource.
182 // method - HTTP method, one of GET, HEAD, PUT, POST or DELETE
183 // resource - the arvados resource to act on
184 // uuid - the uuid of the specific item to access (may be empty)
185 // action - sub-action to take on the resource or uuid (may be empty)
186 // parameters - method parameters
187 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
189 // err - error accessing the resource, or nil if no error
190 func (this ArvadosClient) Call(method string, resource string, uuid string, action string, parameters Dict, output interface{}) (err error) {
191 var reader io.ReadCloser
192 reader, err = this.CallRaw(method, resource, uuid, action, parameters)
201 dec := json.NewDecoder(reader)
202 if err = dec.Decode(output); err != nil {
209 // Create a new instance of a resource.
211 // resource - the arvados resource on which to create an item
212 // parameters - method parameters
213 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
215 // err - error accessing the resource, or nil if no error
216 func (this ArvadosClient) Create(resource string, parameters Dict, output interface{}) (err error) {
217 return this.Call("POST", resource, "", "", parameters, output)
220 // Delete an instance of a resource.
222 // resource - the arvados resource on which to delete an item
223 // uuid - the item to delete
224 // parameters - method parameters
225 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
227 // err - error accessing the resource, or nil if no error
228 func (this ArvadosClient) Delete(resource string, uuid string, parameters Dict, output interface{}) (err error) {
229 return this.Call("DELETE", resource, uuid, "", parameters, output)
232 // Update fields of an instance of a resource.
234 // resource - the arvados resource on which to update the item
235 // uuid - the item to update
236 // parameters - method parameters
237 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
239 // err - error accessing the resource, or nil if no error
240 func (this ArvadosClient) Update(resource string, uuid string, parameters Dict, output interface{}) (err error) {
241 return this.Call("PUT", resource, uuid, "", parameters, output)
244 // List the instances of a resource
246 // resource - the arvados resource on which to list
247 // parameters - method parameters
248 // output - a map or annotated struct which is a legal target for encoding/json/Decoder
250 // err - error accessing the resource, or nil if no error
251 func (this ArvadosClient) List(resource string, parameters Dict, output interface{}) (err error) {
252 return this.Call("GET", resource, "", "", parameters, output)
257 // parameter - name of parameter to be discovered
259 // valueMap - Dict key value pair of the discovered parameter
260 // err - error accessing the resource, or nil if no error
261 var API_DISCOVERY_RESOURCE string = "discovery/v1/apis/arvados/v1/rest"
265 func (this ArvadosClient) Discovery(parameter string) (valueMap Dict, err error) {
266 if len(DISCOVERY) == 0 {
267 DISCOVERY = make(Dict)
268 this.Call("GET", API_DISCOVERY_RESOURCE, "", "", nil, &DISCOVERY)
271 valueMap = make(Dict)
272 valueMap[parameter] = DISCOVERY[parameter]