1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
22 "git.arvados.org/arvados.git/sdk/go/arvados"
23 "git.arvados.org/arvados.git/sdk/go/auth"
26 const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
28 type TokenProvider func(context.Context) ([]string, error)
30 func PassthroughTokenProvider(ctx context.Context) ([]string, error) {
31 incoming, ok := auth.FromContext(ctx)
33 return nil, errors.New("no token provided")
35 return incoming.Tokens, nil
39 SendHeader http.Header
41 httpClient http.Client
43 tokenProvider TokenProvider
46 func NewConn(clusterID string, url *url.URL, insecure bool, tp TokenProvider) *Conn {
47 transport := http.DefaultTransport
49 // It's not safe to copy *http.DefaultTransport
50 // because it has a mutex (which might be locked)
51 // protecting a private map (which might not be nil).
52 // So we build our own, using the Go 1.12 default
53 // values, ignoring any changes the application has
54 // made to http.DefaultTransport.
55 transport = &http.Transport{
56 DialContext: (&net.Dialer{
57 Timeout: 30 * time.Second,
58 KeepAlive: 30 * time.Second,
62 IdleConnTimeout: 90 * time.Second,
63 TLSHandshakeTimeout: 10 * time.Second,
64 ExpectContinueTimeout: 1 * time.Second,
65 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
70 httpClient: http.Client{
71 CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
79 func (conn *Conn) requestAndDecode(ctx context.Context, dst interface{}, ep arvados.APIEndpoint, body io.Reader, opts interface{}) error {
80 aClient := arvados.Client{
81 Client: &conn.httpClient,
82 Scheme: conn.baseURL.Scheme,
83 APIHost: conn.baseURL.Host,
84 SendHeader: conn.SendHeader,
86 tokens, err := conn.tokenProvider(ctx)
89 } else if len(tokens) > 0 {
90 ctx = arvados.ContextWithAuthorization(ctx, "Bearer "+tokens[0])
92 // Use a non-empty auth string to ensure we override
93 // any default token set on aClient -- and to avoid
94 // having the remote prompt us to send a token by
96 ctx = arvados.ContextWithAuthorization(ctx, "Bearer -")
99 // Encode opts to JSON and decode from there to a
100 // map[string]interface{}, so we can munge the query params
101 // using the JSON key names specified by opts' struct tags.
102 j, err := json.Marshal(opts)
104 return fmt.Errorf("%T: requestAndDecode: Marshal opts: %s", conn, err)
106 var params map[string]interface{}
107 dec := json.NewDecoder(bytes.NewBuffer(j))
109 err = dec.Decode(¶ms)
111 return fmt.Errorf("%T: requestAndDecode: Decode opts: %s", conn, err)
113 if attrs, ok := params["attrs"]; ok && ep.AttrsKey != "" {
114 params[ep.AttrsKey] = attrs
115 delete(params, "attrs")
117 if limitStr, ok := params["limit"]; ok {
118 if limit, err := strconv.ParseInt(string(limitStr.(json.Number)), 10, 64); err == nil && limit < 0 {
119 // Negative limit means "not specified" here, but some
120 // servers/versions do not accept that, so we need to
121 // remove it entirely.
122 delete(params, "limit")
126 if authinfo, ok := params["auth_info"]; ok {
127 if tmp, ok2 := authinfo.(map[string]interface{}); ok2 {
128 for k, v := range tmp {
129 if strings.HasSuffix(k, "_at") {
130 // Change zero times values to nil
131 if v, ok3 := v.(string); ok3 && (strings.HasPrefix(v, "0001-01-01T00:00:00") || v == "") {
140 params["reader_tokens"] = tokens[1:]
143 if strings.Contains(ep.Path, "/{uuid}") {
144 uuid, _ := params["uuid"].(string)
145 path = strings.Replace(path, "/{uuid}", "/"+uuid, 1)
146 delete(params, "uuid")
148 return aClient.RequestAndDecodeContext(ctx, dst, ep.Method, path, body, params)
151 func (conn *Conn) BaseURL() url.URL {
155 func (conn *Conn) ConfigGet(ctx context.Context) (json.RawMessage, error) {
156 ep := arvados.EndpointConfigGet
157 var resp json.RawMessage
158 err := conn.requestAndDecode(ctx, &resp, ep, nil, nil)
162 func (conn *Conn) Login(ctx context.Context, options arvados.LoginOptions) (arvados.LoginResponse, error) {
163 ep := arvados.EndpointLogin
164 var resp arvados.LoginResponse
165 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
166 resp.RedirectLocation = conn.relativeToBaseURL(resp.RedirectLocation)
170 func (conn *Conn) Logout(ctx context.Context, options arvados.LogoutOptions) (arvados.LogoutResponse, error) {
171 ep := arvados.EndpointLogout
172 var resp arvados.LogoutResponse
173 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
174 resp.RedirectLocation = conn.relativeToBaseURL(resp.RedirectLocation)
178 // If the given location is a valid URL and its origin is the same as
179 // conn.baseURL, return it as a relative URL. Otherwise, return it
181 func (conn *Conn) relativeToBaseURL(location string) string {
182 u, err := url.Parse(location)
183 if err == nil && u.Scheme == conn.baseURL.Scheme && strings.ToLower(u.Host) == strings.ToLower(conn.baseURL.Host) {
193 func (conn *Conn) CollectionCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Collection, error) {
194 ep := arvados.EndpointCollectionCreate
195 var resp arvados.Collection
196 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
200 func (conn *Conn) CollectionUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Collection, error) {
201 ep := arvados.EndpointCollectionUpdate
202 var resp arvados.Collection
203 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
207 func (conn *Conn) CollectionGet(ctx context.Context, options arvados.GetOptions) (arvados.Collection, error) {
208 ep := arvados.EndpointCollectionGet
209 var resp arvados.Collection
210 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
214 func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
215 ep := arvados.EndpointCollectionList
216 var resp arvados.CollectionList
217 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
221 func (conn *Conn) CollectionProvenance(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
222 ep := arvados.EndpointCollectionProvenance
223 var resp map[string]interface{}
224 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
228 func (conn *Conn) CollectionUsedBy(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
229 ep := arvados.EndpointCollectionUsedBy
230 var resp map[string]interface{}
231 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
235 func (conn *Conn) CollectionDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Collection, error) {
236 ep := arvados.EndpointCollectionDelete
237 var resp arvados.Collection
238 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
242 func (conn *Conn) CollectionTrash(ctx context.Context, options arvados.DeleteOptions) (arvados.Collection, error) {
243 ep := arvados.EndpointCollectionTrash
244 var resp arvados.Collection
245 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
249 func (conn *Conn) CollectionUntrash(ctx context.Context, options arvados.UntrashOptions) (arvados.Collection, error) {
250 ep := arvados.EndpointCollectionUntrash
251 var resp arvados.Collection
252 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
256 func (conn *Conn) ContainerCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Container, error) {
257 ep := arvados.EndpointContainerCreate
258 var resp arvados.Container
259 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
263 func (conn *Conn) ContainerUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Container, error) {
264 ep := arvados.EndpointContainerUpdate
265 var resp arvados.Container
266 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
270 func (conn *Conn) ContainerGet(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
271 ep := arvados.EndpointContainerGet
272 var resp arvados.Container
273 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
277 func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
278 ep := arvados.EndpointContainerList
279 var resp arvados.ContainerList
280 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
284 func (conn *Conn) ContainerDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Container, error) {
285 ep := arvados.EndpointContainerDelete
286 var resp arvados.Container
287 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
291 func (conn *Conn) ContainerLock(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
292 ep := arvados.EndpointContainerLock
293 var resp arvados.Container
294 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
298 func (conn *Conn) ContainerUnlock(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
299 ep := arvados.EndpointContainerUnlock
300 var resp arvados.Container
301 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
305 func (conn *Conn) ContainerRequestCreate(ctx context.Context, options arvados.CreateOptions) (arvados.ContainerRequest, error) {
306 ep := arvados.EndpointContainerRequestCreate
307 var resp arvados.ContainerRequest
308 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
312 func (conn *Conn) ContainerRequestUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.ContainerRequest, error) {
313 ep := arvados.EndpointContainerRequestUpdate
314 var resp arvados.ContainerRequest
315 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
319 func (conn *Conn) ContainerRequestGet(ctx context.Context, options arvados.GetOptions) (arvados.ContainerRequest, error) {
320 ep := arvados.EndpointContainerRequestGet
321 var resp arvados.ContainerRequest
322 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
326 func (conn *Conn) ContainerRequestList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerRequestList, error) {
327 ep := arvados.EndpointContainerRequestList
328 var resp arvados.ContainerRequestList
329 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
333 func (conn *Conn) ContainerRequestDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.ContainerRequest, error) {
334 ep := arvados.EndpointContainerRequestDelete
335 var resp arvados.ContainerRequest
336 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
340 func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
341 ep := arvados.EndpointSpecimenCreate
342 var resp arvados.Specimen
343 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
347 func (conn *Conn) SpecimenUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Specimen, error) {
348 ep := arvados.EndpointSpecimenUpdate
349 var resp arvados.Specimen
350 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
354 func (conn *Conn) SpecimenGet(ctx context.Context, options arvados.GetOptions) (arvados.Specimen, error) {
355 ep := arvados.EndpointSpecimenGet
356 var resp arvados.Specimen
357 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
361 func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
362 ep := arvados.EndpointSpecimenList
363 var resp arvados.SpecimenList
364 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
368 func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Specimen, error) {
369 ep := arvados.EndpointSpecimenDelete
370 var resp arvados.Specimen
371 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
375 func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
376 ep := arvados.EndpointUserCreate
377 var resp arvados.User
378 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
381 func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
382 ep := arvados.EndpointUserUpdate
383 var resp arvados.User
384 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
387 func (conn *Conn) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
388 ep := arvados.EndpointUserUpdateUUID
389 var resp arvados.User
390 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
393 func (conn *Conn) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
394 ep := arvados.EndpointUserMerge
395 var resp arvados.User
396 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
399 func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
400 ep := arvados.EndpointUserActivate
401 var resp arvados.User
402 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
405 func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
406 ep := arvados.EndpointUserSetup
407 var resp map[string]interface{}
408 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
411 func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
412 ep := arvados.EndpointUserUnsetup
413 var resp arvados.User
414 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
417 func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
418 ep := arvados.EndpointUserGet
419 var resp arvados.User
420 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
423 func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
424 ep := arvados.EndpointUserGetCurrent
425 var resp arvados.User
426 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
429 func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
430 ep := arvados.EndpointUserGetSystem
431 var resp arvados.User
432 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
435 func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
436 ep := arvados.EndpointUserList
437 var resp arvados.UserList
438 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
441 func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
442 ep := arvados.EndpointUserDelete
443 var resp arvados.User
444 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
448 func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
449 ep := arvados.EndpointAPIClientAuthorizationCurrent
450 var resp arvados.APIClientAuthorization
451 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
455 type UserSessionAuthInfo struct {
456 UserUUID string `json:"user_uuid"`
457 Email string `json:"email"`
458 AlternateEmails []string `json:"alternate_emails"`
459 FirstName string `json:"first_name"`
460 LastName string `json:"last_name"`
461 Username string `json:"username"`
462 ExpiresAt time.Time `json:"expires_at"`
465 type UserSessionCreateOptions struct {
466 AuthInfo UserSessionAuthInfo `json:"auth_info"`
467 ReturnTo string `json:"return_to"`
470 func (conn *Conn) UserSessionCreate(ctx context.Context, options UserSessionCreateOptions) (arvados.LoginResponse, error) {
471 ep := arvados.APIEndpoint{Method: "POST", Path: "auth/controller/callback"}
472 var resp arvados.LoginResponse
473 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
477 func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
478 ep := arvados.EndpointUserBatchUpdate
479 var resp arvados.UserList
480 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
484 func (conn *Conn) UserAuthenticate(ctx context.Context, options arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
485 ep := arvados.EndpointUserAuthenticate
486 var resp arvados.APIClientAuthorization
487 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)