17756: Fix tests to accommodate sudo and added bjobs args.
[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) Login(ctx context.Context, options arvados.LoginOptions) (arvados.LoginResponse, error) {
182         ep := arvados.EndpointLogin
183         var resp arvados.LoginResponse
184         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
185         resp.RedirectLocation = conn.relativeToBaseURL(resp.RedirectLocation)
186         return resp, err
187 }
188
189 func (conn *Conn) Logout(ctx context.Context, options arvados.LogoutOptions) (arvados.LogoutResponse, error) {
190         ep := arvados.EndpointLogout
191         var resp arvados.LogoutResponse
192         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
193         resp.RedirectLocation = conn.relativeToBaseURL(resp.RedirectLocation)
194         return resp, err
195 }
196
197 // If the given location is a valid URL and its origin is the same as
198 // conn.baseURL, return it as a relative URL. Otherwise, return it
199 // unmodified.
200 func (conn *Conn) relativeToBaseURL(location string) string {
201         u, err := url.Parse(location)
202         if err == nil && u.Scheme == conn.baseURL.Scheme && strings.ToLower(u.Host) == strings.ToLower(conn.baseURL.Host) {
203                 u.Opaque = ""
204                 u.Scheme = ""
205                 u.User = nil
206                 u.Host = ""
207                 return u.String()
208         }
209         return location
210 }
211
212 func (conn *Conn) CollectionCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Collection, error) {
213         ep := arvados.EndpointCollectionCreate
214         var resp arvados.Collection
215         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
216         return resp, err
217 }
218
219 func (conn *Conn) CollectionUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Collection, error) {
220         ep := arvados.EndpointCollectionUpdate
221         var resp arvados.Collection
222         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
223         return resp, err
224 }
225
226 func (conn *Conn) CollectionGet(ctx context.Context, options arvados.GetOptions) (arvados.Collection, error) {
227         ep := arvados.EndpointCollectionGet
228         var resp arvados.Collection
229         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
230         return resp, err
231 }
232
233 func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
234         ep := arvados.EndpointCollectionList
235         var resp arvados.CollectionList
236         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
237         return resp, err
238 }
239
240 func (conn *Conn) CollectionProvenance(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
241         ep := arvados.EndpointCollectionProvenance
242         var resp map[string]interface{}
243         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
244         return resp, err
245 }
246
247 func (conn *Conn) CollectionUsedBy(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
248         ep := arvados.EndpointCollectionUsedBy
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) CollectionDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Collection, error) {
255         ep := arvados.EndpointCollectionDelete
256         var resp arvados.Collection
257         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
258         return resp, err
259 }
260
261 func (conn *Conn) CollectionTrash(ctx context.Context, options arvados.DeleteOptions) (arvados.Collection, error) {
262         ep := arvados.EndpointCollectionTrash
263         var resp arvados.Collection
264         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
265         return resp, err
266 }
267
268 func (conn *Conn) CollectionUntrash(ctx context.Context, options arvados.UntrashOptions) (arvados.Collection, error) {
269         ep := arvados.EndpointCollectionUntrash
270         var resp arvados.Collection
271         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
272         return resp, err
273 }
274
275 func (conn *Conn) ContainerCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Container, error) {
276         ep := arvados.EndpointContainerCreate
277         var resp arvados.Container
278         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
279         return resp, err
280 }
281
282 func (conn *Conn) ContainerUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Container, error) {
283         ep := arvados.EndpointContainerUpdate
284         var resp arvados.Container
285         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
286         return resp, err
287 }
288
289 func (conn *Conn) ContainerGet(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
290         ep := arvados.EndpointContainerGet
291         var resp arvados.Container
292         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
293         return resp, err
294 }
295
296 func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
297         ep := arvados.EndpointContainerList
298         var resp arvados.ContainerList
299         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
300         return resp, err
301 }
302
303 func (conn *Conn) ContainerDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Container, error) {
304         ep := arvados.EndpointContainerDelete
305         var resp arvados.Container
306         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
307         return resp, err
308 }
309
310 func (conn *Conn) ContainerLock(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
311         ep := arvados.EndpointContainerLock
312         var resp arvados.Container
313         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
314         return resp, err
315 }
316
317 func (conn *Conn) ContainerUnlock(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
318         ep := arvados.EndpointContainerUnlock
319         var resp arvados.Container
320         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
321         return resp, err
322 }
323
324 // ContainerSSH returns a connection to the out-of-band SSH server for
325 // a running container. If the returned error is nil, the caller is
326 // responsible for closing sshconn.Conn.
327 func (conn *Conn) ContainerSSH(ctx context.Context, options arvados.ContainerSSHOptions) (sshconn arvados.ContainerSSHConnection, err error) {
328         addr := conn.baseURL.Host
329         if strings.Index(addr, ":") < 1 || (strings.Contains(addr, "::") && addr[0] != '[') {
330                 // hostname or ::1 or 1::1
331                 addr = net.JoinHostPort(addr, "https")
332         }
333         insecure := false
334         if tlsconf := conn.httpClient.Transport.(*http.Transport).TLSClientConfig; tlsconf != nil && tlsconf.InsecureSkipVerify {
335                 insecure = true
336         }
337         netconn, err := tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: insecure})
338         if err != nil {
339                 err = fmt.Errorf("tls.Dial: %w", err)
340                 return
341         }
342         defer func() {
343                 if err != nil {
344                         netconn.Close()
345                 }
346         }()
347         bufr := bufio.NewReader(netconn)
348         bufw := bufio.NewWriter(netconn)
349
350         u, err := conn.baseURL.Parse("/" + strings.Replace(arvados.EndpointContainerSSH.Path, "{uuid}", options.UUID, -1))
351         if err != nil {
352                 err = fmt.Errorf("tls.Dial: %w", err)
353                 return
354         }
355         u.RawQuery = url.Values{
356                 "detach_keys":    {options.DetachKeys},
357                 "login_username": {options.LoginUsername},
358         }.Encode()
359         tokens, err := conn.tokenProvider(ctx)
360         if err != nil {
361                 return
362         } else if len(tokens) < 1 {
363                 err = httpserver.ErrorWithStatus(errors.New("unauthorized"), http.StatusUnauthorized)
364                 return
365         }
366         bufw.WriteString("GET " + u.String() + " HTTP/1.1\r\n")
367         bufw.WriteString("Authorization: Bearer " + tokens[0] + "\r\n")
368         bufw.WriteString("Host: " + u.Host + "\r\n")
369         bufw.WriteString("Upgrade: ssh\r\n")
370         bufw.WriteString("\r\n")
371         bufw.Flush()
372         resp, err := http.ReadResponse(bufr, &http.Request{Method: "GET"})
373         if err != nil {
374                 err = fmt.Errorf("http.ReadResponse: %w", err)
375                 return
376         }
377         if resp.StatusCode != http.StatusSwitchingProtocols {
378                 defer resp.Body.Close()
379                 body, _ := ioutil.ReadAll(resp.Body)
380                 var message string
381                 var errDoc httpserver.ErrorResponse
382                 if err := json.Unmarshal(body, &errDoc); err == nil {
383                         message = strings.Join(errDoc.Errors, "; ")
384                 } else {
385                         message = fmt.Sprintf("%q", body)
386                 }
387                 err = fmt.Errorf("server did not provide a tunnel: %s (HTTP %d)", message, resp.StatusCode)
388                 return
389         }
390         if strings.ToLower(resp.Header.Get("Upgrade")) != "ssh" ||
391                 strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
392                 err = fmt.Errorf("bad response from server: Upgrade %q Connection %q", resp.Header.Get("Upgrade"), resp.Header.Get("Connection"))
393                 return
394         }
395         sshconn.Conn = netconn
396         sshconn.Bufrw = &bufio.ReadWriter{Reader: bufr, Writer: bufw}
397         return
398 }
399
400 func (conn *Conn) ContainerRequestCreate(ctx context.Context, options arvados.CreateOptions) (arvados.ContainerRequest, error) {
401         ep := arvados.EndpointContainerRequestCreate
402         var resp arvados.ContainerRequest
403         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
404         return resp, err
405 }
406
407 func (conn *Conn) ContainerRequestUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.ContainerRequest, error) {
408         ep := arvados.EndpointContainerRequestUpdate
409         var resp arvados.ContainerRequest
410         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
411         return resp, err
412 }
413
414 func (conn *Conn) ContainerRequestGet(ctx context.Context, options arvados.GetOptions) (arvados.ContainerRequest, error) {
415         ep := arvados.EndpointContainerRequestGet
416         var resp arvados.ContainerRequest
417         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
418         return resp, err
419 }
420
421 func (conn *Conn) ContainerRequestList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerRequestList, error) {
422         ep := arvados.EndpointContainerRequestList
423         var resp arvados.ContainerRequestList
424         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
425         return resp, err
426 }
427
428 func (conn *Conn) ContainerRequestDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.ContainerRequest, error) {
429         ep := arvados.EndpointContainerRequestDelete
430         var resp arvados.ContainerRequest
431         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
432         return resp, err
433 }
434
435 func (conn *Conn) GroupCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Group, error) {
436         ep := arvados.EndpointGroupCreate
437         var resp arvados.Group
438         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
439         return resp, err
440 }
441
442 func (conn *Conn) GroupUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Group, error) {
443         ep := arvados.EndpointGroupUpdate
444         var resp arvados.Group
445         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
446         return resp, err
447 }
448
449 func (conn *Conn) GroupGet(ctx context.Context, options arvados.GetOptions) (arvados.Group, error) {
450         ep := arvados.EndpointGroupGet
451         var resp arvados.Group
452         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
453         return resp, err
454 }
455
456 func (conn *Conn) GroupList(ctx context.Context, options arvados.ListOptions) (arvados.GroupList, error) {
457         ep := arvados.EndpointGroupList
458         var resp arvados.GroupList
459         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
460         return resp, err
461 }
462
463 func (conn *Conn) GroupContents(ctx context.Context, options arvados.GroupContentsOptions) (arvados.ObjectList, error) {
464         ep := arvados.EndpointGroupContents
465         var resp arvados.ObjectList
466         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
467         return resp, err
468 }
469
470 func (conn *Conn) GroupShared(ctx context.Context, options arvados.ListOptions) (arvados.GroupList, error) {
471         ep := arvados.EndpointGroupShared
472         var resp arvados.GroupList
473         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
474         return resp, err
475 }
476
477 func (conn *Conn) GroupDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Group, error) {
478         ep := arvados.EndpointGroupDelete
479         var resp arvados.Group
480         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
481         return resp, err
482 }
483
484 func (conn *Conn) GroupTrash(ctx context.Context, options arvados.DeleteOptions) (arvados.Group, error) {
485         ep := arvados.EndpointGroupTrash
486         var resp arvados.Group
487         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
488         return resp, err
489 }
490
491 func (conn *Conn) GroupUntrash(ctx context.Context, options arvados.UntrashOptions) (arvados.Group, error) {
492         ep := arvados.EndpointGroupUntrash
493         var resp arvados.Group
494         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
495         return resp, err
496 }
497
498 func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
499         ep := arvados.EndpointSpecimenCreate
500         var resp arvados.Specimen
501         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
502         return resp, err
503 }
504
505 func (conn *Conn) SpecimenUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Specimen, error) {
506         ep := arvados.EndpointSpecimenUpdate
507         var resp arvados.Specimen
508         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
509         return resp, err
510 }
511
512 func (conn *Conn) SpecimenGet(ctx context.Context, options arvados.GetOptions) (arvados.Specimen, error) {
513         ep := arvados.EndpointSpecimenGet
514         var resp arvados.Specimen
515         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
516         return resp, err
517 }
518
519 func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
520         ep := arvados.EndpointSpecimenList
521         var resp arvados.SpecimenList
522         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
523         return resp, err
524 }
525
526 func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Specimen, error) {
527         ep := arvados.EndpointSpecimenDelete
528         var resp arvados.Specimen
529         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
530         return resp, err
531 }
532
533 func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
534         ep := arvados.EndpointUserCreate
535         var resp arvados.User
536         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
537         return resp, err
538 }
539 func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
540         ep := arvados.EndpointUserUpdate
541         var resp arvados.User
542         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
543         return resp, err
544 }
545 func (conn *Conn) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
546         ep := arvados.EndpointUserUpdateUUID
547         var resp arvados.User
548         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
549         return resp, err
550 }
551 func (conn *Conn) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
552         ep := arvados.EndpointUserMerge
553         var resp arvados.User
554         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
555         return resp, err
556 }
557 func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
558         ep := arvados.EndpointUserActivate
559         var resp arvados.User
560         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
561         return resp, err
562 }
563 func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
564         ep := arvados.EndpointUserSetup
565         var resp map[string]interface{}
566         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
567         return resp, err
568 }
569 func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
570         ep := arvados.EndpointUserUnsetup
571         var resp arvados.User
572         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
573         return resp, err
574 }
575 func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
576         ep := arvados.EndpointUserGet
577         var resp arvados.User
578         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
579         return resp, err
580 }
581 func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
582         ep := arvados.EndpointUserGetCurrent
583         var resp arvados.User
584         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
585         return resp, err
586 }
587 func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
588         ep := arvados.EndpointUserGetSystem
589         var resp arvados.User
590         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
591         return resp, err
592 }
593 func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
594         ep := arvados.EndpointUserList
595         var resp arvados.UserList
596         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
597         return resp, err
598 }
599 func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
600         ep := arvados.EndpointUserDelete
601         var resp arvados.User
602         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
603         return resp, err
604 }
605
606 func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
607         ep := arvados.EndpointAPIClientAuthorizationCurrent
608         var resp arvados.APIClientAuthorization
609         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
610         return resp, err
611 }
612
613 type UserSessionAuthInfo struct {
614         UserUUID        string    `json:"user_uuid"`
615         Email           string    `json:"email"`
616         AlternateEmails []string  `json:"alternate_emails"`
617         FirstName       string    `json:"first_name"`
618         LastName        string    `json:"last_name"`
619         Username        string    `json:"username"`
620         ExpiresAt       time.Time `json:"expires_at"`
621 }
622
623 type UserSessionCreateOptions struct {
624         AuthInfo UserSessionAuthInfo `json:"auth_info"`
625         ReturnTo string              `json:"return_to"`
626 }
627
628 func (conn *Conn) UserSessionCreate(ctx context.Context, options UserSessionCreateOptions) (arvados.LoginResponse, error) {
629         ep := arvados.APIEndpoint{Method: "POST", Path: "auth/controller/callback"}
630         var resp arvados.LoginResponse
631         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
632         return resp, err
633 }
634
635 func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
636         ep := arvados.EndpointUserBatchUpdate
637         var resp arvados.UserList
638         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
639         return resp, err
640 }
641
642 func (conn *Conn) UserAuthenticate(ctx context.Context, options arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
643         ep := arvados.EndpointUserAuthenticate
644         var resp arvados.APIClientAuthorization
645         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
646         return resp, err
647 }
648
649 // httpStatusError is an error with an HTTP status code that can be
650 // propagated by lib/controller/router, etc.
651 type httpStatusError interface {
652         error
653         HTTPStatus() int
654 }
655
656 // wrappedHTTPStatusError is used to augment/replace an error message
657 // while preserving the HTTP status code indicated by the original
658 // error.
659 type wrappedHTTPStatusError struct {
660         httpStatusError
661         message string
662 }
663
664 func wrapHTTPStatusError(err httpStatusError, message string) httpStatusError {
665         return wrappedHTTPStatusError{err, message}
666 }
667
668 func (err wrappedHTTPStatusError) Error() string {
669         return err.message
670 }