17840: Merge branch 'main'
[arvados.git] / lib / controller / rpc / conn.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package rpc
6
7 import (
8         "bufio"
9         "bytes"
10         "context"
11         "crypto/tls"
12         "encoding/json"
13         "errors"
14         "fmt"
15         "io"
16         "io/ioutil"
17         "net"
18         "net/http"
19         "net/url"
20         "strconv"
21         "strings"
22         "time"
23
24         "git.arvados.org/arvados.git/sdk/go/arvados"
25         "git.arvados.org/arvados.git/sdk/go/auth"
26         "git.arvados.org/arvados.git/sdk/go/httpserver"
27 )
28
29 const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
30
31 type TokenProvider func(context.Context) ([]string, error)
32
33 func PassthroughTokenProvider(ctx context.Context) ([]string, error) {
34         incoming, ok := auth.FromContext(ctx)
35         if !ok {
36                 return nil, errors.New("no token provided")
37         }
38         return incoming.Tokens, nil
39 }
40
41 type Conn struct {
42         SendHeader         http.Header
43         RedactHostInErrors bool
44
45         clusterID     string
46         httpClient    http.Client
47         baseURL       url.URL
48         tokenProvider TokenProvider
49 }
50
51 func NewConn(clusterID string, url *url.URL, insecure bool, tp TokenProvider) *Conn {
52         transport := http.DefaultTransport
53         if insecure {
54                 // It's not safe to copy *http.DefaultTransport
55                 // because it has a mutex (which might be locked)
56                 // protecting a private map (which might not be nil).
57                 // So we build our own, using the Go 1.12 default
58                 // values, ignoring any changes the application has
59                 // made to http.DefaultTransport.
60                 transport = &http.Transport{
61                         DialContext: (&net.Dialer{
62                                 Timeout:   30 * time.Second,
63                                 KeepAlive: 30 * time.Second,
64                                 DualStack: true,
65                         }).DialContext,
66                         MaxIdleConns:          100,
67                         IdleConnTimeout:       90 * time.Second,
68                         TLSHandshakeTimeout:   10 * time.Second,
69                         ExpectContinueTimeout: 1 * time.Second,
70                         TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
71                 }
72         }
73         return &Conn{
74                 clusterID: clusterID,
75                 httpClient: http.Client{
76                         CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
77                         Transport:     transport,
78                 },
79                 baseURL:       *url,
80                 tokenProvider: tp,
81         }
82 }
83
84 func (conn *Conn) requestAndDecode(ctx context.Context, dst interface{}, ep arvados.APIEndpoint, body io.Reader, opts interface{}) error {
85         aClient := arvados.Client{
86                 Client:     &conn.httpClient,
87                 Scheme:     conn.baseURL.Scheme,
88                 APIHost:    conn.baseURL.Host,
89                 SendHeader: conn.SendHeader,
90         }
91         tokens, err := conn.tokenProvider(ctx)
92         if err != nil {
93                 return err
94         } else if len(tokens) > 0 {
95                 ctx = arvados.ContextWithAuthorization(ctx, "Bearer "+tokens[0])
96         } else {
97                 // Use a non-empty auth string to ensure we override
98                 // any default token set on aClient -- and to avoid
99                 // having the remote prompt us to send a token by
100                 // responding 401.
101                 ctx = arvados.ContextWithAuthorization(ctx, "Bearer -")
102         }
103
104         // Encode opts to JSON and decode from there to a
105         // map[string]interface{}, so we can munge the query params
106         // using the JSON key names specified by opts' struct tags.
107         j, err := json.Marshal(opts)
108         if err != nil {
109                 return fmt.Errorf("%T: requestAndDecode: Marshal opts: %s", conn, err)
110         }
111         var params map[string]interface{}
112         dec := json.NewDecoder(bytes.NewBuffer(j))
113         dec.UseNumber()
114         err = dec.Decode(&params)
115         if err != nil {
116                 return fmt.Errorf("%T: requestAndDecode: Decode opts: %s", conn, err)
117         }
118         if attrs, ok := params["attrs"]; ok && ep.AttrsKey != "" {
119                 params[ep.AttrsKey] = attrs
120                 delete(params, "attrs")
121         }
122         if limitStr, ok := params["limit"]; ok {
123                 if limit, err := strconv.ParseInt(string(limitStr.(json.Number)), 10, 64); err == nil && limit < 0 {
124                         // Negative limit means "not specified" here, but some
125                         // servers/versions do not accept that, so we need to
126                         // remove it entirely.
127                         delete(params, "limit")
128                 }
129         }
130
131         if authinfo, ok := params["auth_info"]; ok {
132                 if tmp, ok2 := authinfo.(map[string]interface{}); ok2 {
133                         for k, v := range tmp {
134                                 if strings.HasSuffix(k, "_at") {
135                                         // Change zero times values to nil
136                                         if v, ok3 := v.(string); ok3 && (strings.HasPrefix(v, "0001-01-01T00:00:00") || v == "") {
137                                                 tmp[k] = nil
138                                         }
139                                 }
140                         }
141                 }
142         }
143
144         if len(tokens) > 1 {
145                 params["reader_tokens"] = tokens[1:]
146         }
147         path := ep.Path
148         if strings.Contains(ep.Path, "/{uuid}") {
149                 uuid, _ := params["uuid"].(string)
150                 path = strings.Replace(path, "/{uuid}", "/"+uuid, 1)
151                 delete(params, "uuid")
152         }
153         err = aClient.RequestAndDecodeContext(ctx, dst, ep.Method, path, body, params)
154         if err != nil && conn.RedactHostInErrors {
155                 redacted := strings.Replace(err.Error(), strings.TrimSuffix(conn.baseURL.String(), "/"), "//railsapi.internal", -1)
156                 if strings.HasPrefix(redacted, "request failed: ") {
157                         redacted = strings.Replace(redacted, "request failed: ", "", -1)
158                 }
159                 if redacted != err.Error() {
160                         if err, ok := err.(httpStatusError); ok {
161                                 return wrapHTTPStatusError(err, redacted)
162                         } else {
163                                 return errors.New(redacted)
164                         }
165                 }
166         }
167         return err
168 }
169
170 func (conn *Conn) BaseURL() url.URL {
171         return conn.baseURL
172 }
173
174 func (conn *Conn) ConfigGet(ctx context.Context) (json.RawMessage, error) {
175         ep := arvados.EndpointConfigGet
176         var resp json.RawMessage
177         err := conn.requestAndDecode(ctx, &resp, ep, nil, nil)
178         return resp, err
179 }
180
181 func (conn *Conn) VocabularyGet(ctx context.Context) (arvados.Vocabulary, error) {
182         ep := arvados.EndpointVocabularyGet
183         var resp arvados.Vocabulary
184         err := conn.requestAndDecode(ctx, &resp, ep, nil, nil)
185         return resp, err
186 }
187
188 func (conn *Conn) Login(ctx context.Context, options arvados.LoginOptions) (arvados.LoginResponse, error) {
189         ep := arvados.EndpointLogin
190         var resp arvados.LoginResponse
191         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
192         resp.RedirectLocation = conn.relativeToBaseURL(resp.RedirectLocation)
193         return resp, err
194 }
195
196 func (conn *Conn) Logout(ctx context.Context, options arvados.LogoutOptions) (arvados.LogoutResponse, error) {
197         ep := arvados.EndpointLogout
198         var resp arvados.LogoutResponse
199         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
200         resp.RedirectLocation = conn.relativeToBaseURL(resp.RedirectLocation)
201         return resp, err
202 }
203
204 // If the given location is a valid URL and its origin is the same as
205 // conn.baseURL, return it as a relative URL. Otherwise, return it
206 // unmodified.
207 func (conn *Conn) relativeToBaseURL(location string) string {
208         u, err := url.Parse(location)
209         if err == nil && u.Scheme == conn.baseURL.Scheme && strings.ToLower(u.Host) == strings.ToLower(conn.baseURL.Host) {
210                 u.Opaque = ""
211                 u.Scheme = ""
212                 u.User = nil
213                 u.Host = ""
214                 return u.String()
215         }
216         return location
217 }
218
219 func (conn *Conn) CollectionCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Collection, error) {
220         ep := arvados.EndpointCollectionCreate
221         var resp arvados.Collection
222         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
223         return resp, err
224 }
225
226 func (conn *Conn) CollectionUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Collection, error) {
227         ep := arvados.EndpointCollectionUpdate
228         var resp arvados.Collection
229         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
230         return resp, err
231 }
232
233 func (conn *Conn) CollectionGet(ctx context.Context, options arvados.GetOptions) (arvados.Collection, error) {
234         ep := arvados.EndpointCollectionGet
235         var resp arvados.Collection
236         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
237         return resp, err
238 }
239
240 func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
241         ep := arvados.EndpointCollectionList
242         var resp arvados.CollectionList
243         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
244         return resp, err
245 }
246
247 func (conn *Conn) CollectionProvenance(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
248         ep := arvados.EndpointCollectionProvenance
249         var resp map[string]interface{}
250         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
251         return resp, err
252 }
253
254 func (conn *Conn) CollectionUsedBy(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
255         ep := arvados.EndpointCollectionUsedBy
256         var resp map[string]interface{}
257         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
258         return resp, err
259 }
260
261 func (conn *Conn) CollectionDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Collection, error) {
262         ep := arvados.EndpointCollectionDelete
263         var resp arvados.Collection
264         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
265         return resp, err
266 }
267
268 func (conn *Conn) CollectionTrash(ctx context.Context, options arvados.DeleteOptions) (arvados.Collection, error) {
269         ep := arvados.EndpointCollectionTrash
270         var resp arvados.Collection
271         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
272         return resp, err
273 }
274
275 func (conn *Conn) CollectionUntrash(ctx context.Context, options arvados.UntrashOptions) (arvados.Collection, error) {
276         ep := arvados.EndpointCollectionUntrash
277         var resp arvados.Collection
278         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
279         return resp, err
280 }
281
282 func (conn *Conn) ContainerCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Container, error) {
283         ep := arvados.EndpointContainerCreate
284         var resp arvados.Container
285         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
286         return resp, err
287 }
288
289 func (conn *Conn) ContainerUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Container, error) {
290         ep := arvados.EndpointContainerUpdate
291         var resp arvados.Container
292         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
293         return resp, err
294 }
295
296 func (conn *Conn) ContainerGet(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
297         ep := arvados.EndpointContainerGet
298         var resp arvados.Container
299         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
300         return resp, err
301 }
302
303 func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
304         ep := arvados.EndpointContainerList
305         var resp arvados.ContainerList
306         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
307         return resp, err
308 }
309
310 func (conn *Conn) ContainerDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Container, error) {
311         ep := arvados.EndpointContainerDelete
312         var resp arvados.Container
313         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
314         return resp, err
315 }
316
317 func (conn *Conn) ContainerLock(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
318         ep := arvados.EndpointContainerLock
319         var resp arvados.Container
320         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
321         return resp, err
322 }
323
324 func (conn *Conn) ContainerUnlock(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
325         ep := arvados.EndpointContainerUnlock
326         var resp arvados.Container
327         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
328         return resp, err
329 }
330
331 // ContainerSSH returns a connection to the out-of-band SSH server for
332 // a running container. If the returned error is nil, the caller is
333 // responsible for closing sshconn.Conn.
334 func (conn *Conn) ContainerSSH(ctx context.Context, options arvados.ContainerSSHOptions) (sshconn arvados.ContainerSSHConnection, err error) {
335         addr := conn.baseURL.Host
336         if strings.Index(addr, ":") < 1 || (strings.Contains(addr, "::") && addr[0] != '[') {
337                 // hostname or ::1 or 1::1
338                 addr = net.JoinHostPort(addr, "https")
339         }
340         insecure := false
341         if tlsconf := conn.httpClient.Transport.(*http.Transport).TLSClientConfig; tlsconf != nil && tlsconf.InsecureSkipVerify {
342                 insecure = true
343         }
344         netconn, err := tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: insecure})
345         if err != nil {
346                 err = fmt.Errorf("tls.Dial: %w", err)
347                 return
348         }
349         defer func() {
350                 if err != nil {
351                         netconn.Close()
352                 }
353         }()
354         bufr := bufio.NewReader(netconn)
355         bufw := bufio.NewWriter(netconn)
356
357         u, err := conn.baseURL.Parse("/" + strings.Replace(arvados.EndpointContainerSSH.Path, "{uuid}", options.UUID, -1))
358         if err != nil {
359                 err = fmt.Errorf("tls.Dial: %w", err)
360                 return
361         }
362         u.RawQuery = url.Values{
363                 "detach_keys":    {options.DetachKeys},
364                 "login_username": {options.LoginUsername},
365         }.Encode()
366         tokens, err := conn.tokenProvider(ctx)
367         if err != nil {
368                 return
369         } else if len(tokens) < 1 {
370                 err = httpserver.ErrorWithStatus(errors.New("unauthorized"), http.StatusUnauthorized)
371                 return
372         }
373         bufw.WriteString("GET " + u.String() + " HTTP/1.1\r\n")
374         bufw.WriteString("Authorization: Bearer " + tokens[0] + "\r\n")
375         bufw.WriteString("Host: " + u.Host + "\r\n")
376         bufw.WriteString("Upgrade: ssh\r\n")
377         bufw.WriteString("\r\n")
378         bufw.Flush()
379         resp, err := http.ReadResponse(bufr, &http.Request{Method: "GET"})
380         if err != nil {
381                 err = fmt.Errorf("http.ReadResponse: %w", err)
382                 return
383         }
384         if resp.StatusCode != http.StatusSwitchingProtocols {
385                 defer resp.Body.Close()
386                 body, _ := ioutil.ReadAll(resp.Body)
387                 var message string
388                 var errDoc httpserver.ErrorResponse
389                 if err := json.Unmarshal(body, &errDoc); err == nil {
390                         message = strings.Join(errDoc.Errors, "; ")
391                 } else {
392                         message = fmt.Sprintf("%q", body)
393                 }
394                 err = fmt.Errorf("server did not provide a tunnel: %s (HTTP %d)", message, resp.StatusCode)
395                 return
396         }
397         if strings.ToLower(resp.Header.Get("Upgrade")) != "ssh" ||
398                 strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
399                 err = fmt.Errorf("bad response from server: Upgrade %q Connection %q", resp.Header.Get("Upgrade"), resp.Header.Get("Connection"))
400                 return
401         }
402         sshconn.Conn = netconn
403         sshconn.Bufrw = &bufio.ReadWriter{Reader: bufr, Writer: bufw}
404         return
405 }
406
407 func (conn *Conn) ContainerRequestCreate(ctx context.Context, options arvados.CreateOptions) (arvados.ContainerRequest, error) {
408         ep := arvados.EndpointContainerRequestCreate
409         var resp arvados.ContainerRequest
410         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
411         return resp, err
412 }
413
414 func (conn *Conn) ContainerRequestUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.ContainerRequest, error) {
415         ep := arvados.EndpointContainerRequestUpdate
416         var resp arvados.ContainerRequest
417         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
418         return resp, err
419 }
420
421 func (conn *Conn) ContainerRequestGet(ctx context.Context, options arvados.GetOptions) (arvados.ContainerRequest, error) {
422         ep := arvados.EndpointContainerRequestGet
423         var resp arvados.ContainerRequest
424         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
425         return resp, err
426 }
427
428 func (conn *Conn) ContainerRequestList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerRequestList, error) {
429         ep := arvados.EndpointContainerRequestList
430         var resp arvados.ContainerRequestList
431         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
432         return resp, err
433 }
434
435 func (conn *Conn) ContainerRequestDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.ContainerRequest, error) {
436         ep := arvados.EndpointContainerRequestDelete
437         var resp arvados.ContainerRequest
438         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
439         return resp, err
440 }
441
442 func (conn *Conn) GroupCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Group, error) {
443         ep := arvados.EndpointGroupCreate
444         var resp arvados.Group
445         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
446         return resp, err
447 }
448
449 func (conn *Conn) GroupUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Group, error) {
450         ep := arvados.EndpointGroupUpdate
451         var resp arvados.Group
452         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
453         return resp, err
454 }
455
456 func (conn *Conn) GroupGet(ctx context.Context, options arvados.GetOptions) (arvados.Group, error) {
457         ep := arvados.EndpointGroupGet
458         var resp arvados.Group
459         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
460         return resp, err
461 }
462
463 func (conn *Conn) GroupList(ctx context.Context, options arvados.ListOptions) (arvados.GroupList, error) {
464         ep := arvados.EndpointGroupList
465         var resp arvados.GroupList
466         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
467         return resp, err
468 }
469
470 func (conn *Conn) GroupContents(ctx context.Context, options arvados.GroupContentsOptions) (arvados.ObjectList, error) {
471         ep := arvados.EndpointGroupContents
472         var resp arvados.ObjectList
473         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
474         return resp, err
475 }
476
477 func (conn *Conn) GroupShared(ctx context.Context, options arvados.ListOptions) (arvados.GroupList, error) {
478         ep := arvados.EndpointGroupShared
479         var resp arvados.GroupList
480         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
481         return resp, err
482 }
483
484 func (conn *Conn) GroupDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Group, error) {
485         ep := arvados.EndpointGroupDelete
486         var resp arvados.Group
487         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
488         return resp, err
489 }
490
491 func (conn *Conn) GroupTrash(ctx context.Context, options arvados.DeleteOptions) (arvados.Group, error) {
492         ep := arvados.EndpointGroupTrash
493         var resp arvados.Group
494         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
495         return resp, err
496 }
497
498 func (conn *Conn) GroupUntrash(ctx context.Context, options arvados.UntrashOptions) (arvados.Group, error) {
499         ep := arvados.EndpointGroupUntrash
500         var resp arvados.Group
501         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
502         return resp, err
503 }
504
505 func (conn *Conn) LinkCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Link, error) {
506         ep := arvados.EndpointLinkCreate
507         var resp arvados.Link
508         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
509         return resp, err
510 }
511
512 func (conn *Conn) LinkUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Link, error) {
513         ep := arvados.EndpointLinkUpdate
514         var resp arvados.Link
515         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
516         return resp, err
517 }
518
519 func (conn *Conn) LinkGet(ctx context.Context, options arvados.GetOptions) (arvados.Link, error) {
520         ep := arvados.EndpointLinkGet
521         var resp arvados.Link
522         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
523         return resp, err
524 }
525
526 func (conn *Conn) LinkList(ctx context.Context, options arvados.ListOptions) (arvados.LinkList, error) {
527         ep := arvados.EndpointLinkList
528         var resp arvados.LinkList
529         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
530         return resp, err
531 }
532
533 func (conn *Conn) LinkDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Link, error) {
534         ep := arvados.EndpointLinkDelete
535         var resp arvados.Link
536         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
537         return resp, err
538 }
539
540 func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
541         ep := arvados.EndpointSpecimenCreate
542         var resp arvados.Specimen
543         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
544         return resp, err
545 }
546
547 func (conn *Conn) SpecimenUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Specimen, error) {
548         ep := arvados.EndpointSpecimenUpdate
549         var resp arvados.Specimen
550         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
551         return resp, err
552 }
553
554 func (conn *Conn) SpecimenGet(ctx context.Context, options arvados.GetOptions) (arvados.Specimen, error) {
555         ep := arvados.EndpointSpecimenGet
556         var resp arvados.Specimen
557         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
558         return resp, err
559 }
560
561 func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
562         ep := arvados.EndpointSpecimenList
563         var resp arvados.SpecimenList
564         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
565         return resp, err
566 }
567
568 func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Specimen, error) {
569         ep := arvados.EndpointSpecimenDelete
570         var resp arvados.Specimen
571         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
572         return resp, err
573 }
574
575 func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
576         ep := arvados.EndpointUserCreate
577         var resp arvados.User
578         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
579         return resp, err
580 }
581 func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
582         ep := arvados.EndpointUserUpdate
583         var resp arvados.User
584         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
585         return resp, err
586 }
587 func (conn *Conn) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
588         ep := arvados.EndpointUserMerge
589         var resp arvados.User
590         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
591         return resp, err
592 }
593 func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
594         ep := arvados.EndpointUserActivate
595         var resp arvados.User
596         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
597         return resp, err
598 }
599 func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
600         ep := arvados.EndpointUserSetup
601         var resp map[string]interface{}
602         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
603         return resp, err
604 }
605 func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
606         ep := arvados.EndpointUserUnsetup
607         var resp arvados.User
608         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
609         return resp, err
610 }
611 func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
612         ep := arvados.EndpointUserGet
613         var resp arvados.User
614         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
615         return resp, err
616 }
617 func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
618         ep := arvados.EndpointUserGetCurrent
619         var resp arvados.User
620         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
621         return resp, err
622 }
623 func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
624         ep := arvados.EndpointUserGetSystem
625         var resp arvados.User
626         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
627         return resp, err
628 }
629 func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
630         ep := arvados.EndpointUserList
631         var resp arvados.UserList
632         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
633         return resp, err
634 }
635 func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
636         ep := arvados.EndpointUserDelete
637         var resp arvados.User
638         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
639         return resp, err
640 }
641
642 func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
643         ep := arvados.EndpointAPIClientAuthorizationCurrent
644         var resp arvados.APIClientAuthorization
645         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
646         return resp, err
647 }
648
649 type UserSessionAuthInfo struct {
650         UserUUID        string    `json:"user_uuid"`
651         Email           string    `json:"email"`
652         AlternateEmails []string  `json:"alternate_emails"`
653         FirstName       string    `json:"first_name"`
654         LastName        string    `json:"last_name"`
655         Username        string    `json:"username"`
656         ExpiresAt       time.Time `json:"expires_at"`
657 }
658
659 type UserSessionCreateOptions struct {
660         AuthInfo UserSessionAuthInfo `json:"auth_info"`
661         ReturnTo string              `json:"return_to"`
662 }
663
664 func (conn *Conn) UserSessionCreate(ctx context.Context, options UserSessionCreateOptions) (arvados.LoginResponse, error) {
665         ep := arvados.APIEndpoint{Method: "POST", Path: "auth/controller/callback"}
666         var resp arvados.LoginResponse
667         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
668         return resp, err
669 }
670
671 func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
672         ep := arvados.EndpointUserBatchUpdate
673         var resp arvados.UserList
674         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
675         return resp, err
676 }
677
678 func (conn *Conn) UserAuthenticate(ctx context.Context, options arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
679         ep := arvados.EndpointUserAuthenticate
680         var resp arvados.APIClientAuthorization
681         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
682         return resp, err
683 }
684
685 // httpStatusError is an error with an HTTP status code that can be
686 // propagated by lib/controller/router, etc.
687 type httpStatusError interface {
688         error
689         HTTPStatus() int
690 }
691
692 // wrappedHTTPStatusError is used to augment/replace an error message
693 // while preserving the HTTP status code indicated by the original
694 // error.
695 type wrappedHTTPStatusError struct {
696         httpStatusError
697         message string
698 }
699
700 func wrapHTTPStatusError(err httpStatusError, message string) httpStatusError {
701         return wrappedHTTPStatusError{err, message}
702 }
703
704 func (err wrappedHTTPStatusError) Error() string {
705         return err.message
706 }