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 type TokenProvider func(context.Context) ([]string, error)
28 func PassthroughTokenProvider(ctx context.Context) ([]string, error) {
29 if incoming, ok := auth.FromContext(ctx); !ok {
30 return nil, errors.New("no token provided")
32 return incoming.Tokens, nil
36 SendHeader http.Header
38 httpClient http.Client
40 tokenProvider TokenProvider
43 func NewConn(clusterID string, url *url.URL, insecure bool, tp TokenProvider) *Conn {
44 transport := http.DefaultTransport
46 // It's not safe to copy *http.DefaultTransport
47 // because it has a mutex (which might be locked)
48 // protecting a private map (which might not be nil).
49 // So we build our own, using the Go 1.12 default
50 // values, ignoring any changes the application has
51 // made to http.DefaultTransport.
52 transport = &http.Transport{
53 DialContext: (&net.Dialer{
54 Timeout: 30 * time.Second,
55 KeepAlive: 30 * time.Second,
59 IdleConnTimeout: 90 * time.Second,
60 TLSHandshakeTimeout: 10 * time.Second,
61 ExpectContinueTimeout: 1 * time.Second,
62 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
67 httpClient: http.Client{
68 CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
76 func (conn *Conn) requestAndDecode(ctx context.Context, dst interface{}, ep arvados.APIEndpoint, body io.Reader, opts interface{}) error {
77 aClient := arvados.Client{
78 Client: &conn.httpClient,
79 Scheme: conn.baseURL.Scheme,
80 APIHost: conn.baseURL.Host,
81 SendHeader: conn.SendHeader,
83 tokens, err := conn.tokenProvider(ctx)
86 } else if len(tokens) > 0 {
87 ctx = arvados.ContextWithAuthorization(ctx, "Bearer "+tokens[0])
89 // Use a non-empty auth string to ensure we override
90 // any default token set on aClient -- and to avoid
91 // having the remote prompt us to send a token by
93 ctx = arvados.ContextWithAuthorization(ctx, "Bearer -")
96 // Encode opts to JSON and decode from there to a
97 // map[string]interface{}, so we can munge the query params
98 // using the JSON key names specified by opts' struct tags.
99 j, err := json.Marshal(opts)
101 return fmt.Errorf("%T: requestAndDecode: Marshal opts: %s", conn, err)
103 var params map[string]interface{}
104 dec := json.NewDecoder(bytes.NewBuffer(j))
106 err = dec.Decode(¶ms)
108 return fmt.Errorf("%T: requestAndDecode: Decode opts: %s", conn, err)
110 if attrs, ok := params["attrs"]; ok && ep.AttrsKey != "" {
111 params[ep.AttrsKey] = attrs
112 delete(params, "attrs")
114 if limitStr, ok := params["limit"]; ok {
115 if limit, err := strconv.ParseInt(string(limitStr.(json.Number)), 10, 64); err == nil && limit < 0 {
116 // Negative limit means "not specified" here, but some
117 // servers/versions do not accept that, so we need to
118 // remove it entirely.
119 delete(params, "limit")
123 params["reader_tokens"] = tokens[1:]
126 if strings.Contains(ep.Path, "/{uuid}") {
127 uuid, _ := params["uuid"].(string)
128 path = strings.Replace(path, "/{uuid}", "/"+uuid, 1)
129 delete(params, "uuid")
131 return aClient.RequestAndDecodeContext(ctx, dst, ep.Method, path, body, params)
134 func (conn *Conn) BaseURL() url.URL {
138 func (conn *Conn) ConfigGet(ctx context.Context) (json.RawMessage, error) {
139 ep := arvados.EndpointConfigGet
140 var resp json.RawMessage
141 err := conn.requestAndDecode(ctx, &resp, ep, nil, nil)
145 func (conn *Conn) Login(ctx context.Context, options arvados.LoginOptions) (arvados.LoginResponse, error) {
146 ep := arvados.EndpointLogin
147 var resp arvados.LoginResponse
148 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
149 resp.RedirectLocation = conn.relativeToBaseURL(resp.RedirectLocation)
153 func (conn *Conn) Logout(ctx context.Context, options arvados.LogoutOptions) (arvados.LogoutResponse, error) {
154 ep := arvados.EndpointLogout
155 var resp arvados.LogoutResponse
156 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
157 resp.RedirectLocation = conn.relativeToBaseURL(resp.RedirectLocation)
161 // If the given location is a valid URL and its origin is the same as
162 // conn.baseURL, return it as a relative URL. Otherwise, return it
164 func (conn *Conn) relativeToBaseURL(location string) string {
165 u, err := url.Parse(location)
166 if err == nil && u.Scheme == conn.baseURL.Scheme && strings.ToLower(u.Host) == strings.ToLower(conn.baseURL.Host) {
176 func (conn *Conn) CollectionCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Collection, error) {
177 ep := arvados.EndpointCollectionCreate
178 var resp arvados.Collection
179 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
183 func (conn *Conn) CollectionUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Collection, error) {
184 ep := arvados.EndpointCollectionUpdate
185 var resp arvados.Collection
186 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
190 func (conn *Conn) CollectionGet(ctx context.Context, options arvados.GetOptions) (arvados.Collection, error) {
191 ep := arvados.EndpointCollectionGet
192 var resp arvados.Collection
193 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
197 func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
198 ep := arvados.EndpointCollectionList
199 var resp arvados.CollectionList
200 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
204 func (conn *Conn) CollectionProvenance(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
205 ep := arvados.EndpointCollectionProvenance
206 var resp map[string]interface{}
207 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
211 func (conn *Conn) CollectionUsedBy(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
212 ep := arvados.EndpointCollectionUsedBy
213 var resp map[string]interface{}
214 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
218 func (conn *Conn) CollectionDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Collection, error) {
219 ep := arvados.EndpointCollectionDelete
220 var resp arvados.Collection
221 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
225 func (conn *Conn) CollectionTrash(ctx context.Context, options arvados.DeleteOptions) (arvados.Collection, error) {
226 ep := arvados.EndpointCollectionTrash
227 var resp arvados.Collection
228 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
232 func (conn *Conn) CollectionUntrash(ctx context.Context, options arvados.UntrashOptions) (arvados.Collection, error) {
233 ep := arvados.EndpointCollectionUntrash
234 var resp arvados.Collection
235 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
239 func (conn *Conn) ContainerCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Container, error) {
240 ep := arvados.EndpointContainerCreate
241 var resp arvados.Container
242 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
246 func (conn *Conn) ContainerUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Container, error) {
247 ep := arvados.EndpointContainerUpdate
248 var resp arvados.Container
249 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
253 func (conn *Conn) ContainerGet(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
254 ep := arvados.EndpointContainerGet
255 var resp arvados.Container
256 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
260 func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
261 ep := arvados.EndpointContainerList
262 var resp arvados.ContainerList
263 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
267 func (conn *Conn) ContainerDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Container, error) {
268 ep := arvados.EndpointContainerDelete
269 var resp arvados.Container
270 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
274 func (conn *Conn) ContainerLock(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
275 ep := arvados.EndpointContainerLock
276 var resp arvados.Container
277 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
281 func (conn *Conn) ContainerUnlock(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
282 ep := arvados.EndpointContainerUnlock
283 var resp arvados.Container
284 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
288 func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
289 ep := arvados.EndpointSpecimenCreate
290 var resp arvados.Specimen
291 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
295 func (conn *Conn) SpecimenUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Specimen, error) {
296 ep := arvados.EndpointSpecimenUpdate
297 var resp arvados.Specimen
298 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
302 func (conn *Conn) SpecimenGet(ctx context.Context, options arvados.GetOptions) (arvados.Specimen, error) {
303 ep := arvados.EndpointSpecimenGet
304 var resp arvados.Specimen
305 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
309 func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
310 ep := arvados.EndpointSpecimenList
311 var resp arvados.SpecimenList
312 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
316 func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Specimen, error) {
317 ep := arvados.EndpointSpecimenDelete
318 var resp arvados.Specimen
319 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
323 func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
324 ep := arvados.EndpointUserCreate
325 var resp arvados.User
326 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
329 func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
330 ep := arvados.EndpointUserUpdate
331 var resp arvados.User
332 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
335 func (conn *Conn) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
336 ep := arvados.EndpointUserUpdateUUID
337 var resp arvados.User
338 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
341 func (conn *Conn) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
342 ep := arvados.EndpointUserMerge
343 var resp arvados.User
344 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
347 func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
348 ep := arvados.EndpointUserActivate
349 var resp arvados.User
350 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
353 func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
354 ep := arvados.EndpointUserSetup
355 var resp map[string]interface{}
356 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
359 func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
360 ep := arvados.EndpointUserUnsetup
361 var resp arvados.User
362 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
365 func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
366 ep := arvados.EndpointUserGet
367 var resp arvados.User
368 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
371 func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
372 ep := arvados.EndpointUserGetCurrent
373 var resp arvados.User
374 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
377 func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
378 ep := arvados.EndpointUserGetSystem
379 var resp arvados.User
380 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
383 func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
384 ep := arvados.EndpointUserList
385 var resp arvados.UserList
386 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
389 func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
390 ep := arvados.EndpointUserDelete
391 var resp arvados.User
392 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
396 func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
397 ep := arvados.EndpointAPIClientAuthorizationCurrent
398 var resp arvados.APIClientAuthorization
399 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
403 type UserSessionAuthInfo struct {
404 Email string `json:"email"`
405 AlternateEmails []string `json:"alternate_emails"`
406 FirstName string `json:"first_name"`
407 LastName string `json:"last_name"`
408 Username string `json:"username"`
411 type UserSessionCreateOptions struct {
412 AuthInfo UserSessionAuthInfo `json:"auth_info"`
413 ReturnTo string `json:"return_to"`
416 func (conn *Conn) UserSessionCreate(ctx context.Context, options UserSessionCreateOptions) (arvados.LoginResponse, error) {
417 ep := arvados.APIEndpoint{Method: "POST", Path: "auth/controller/callback"}
418 var resp arvados.LoginResponse
419 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
423 func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
424 ep := arvados.EndpointUserBatchUpdate
425 var resp arvados.UserList
426 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
430 func (conn *Conn) UserAuthenticate(ctx context.Context, options arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
431 ep := arvados.EndpointUserAuthenticate
432 var resp arvados.APIClientAuthorization
433 err := conn.requestAndDecode(ctx, &resp, ep, nil, options)