Fix some golint warnings.
[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         "bytes"
9         "context"
10         "crypto/tls"
11         "encoding/json"
12         "errors"
13         "fmt"
14         "io"
15         "net"
16         "net/http"
17         "net/url"
18         "strconv"
19         "strings"
20         "time"
21
22         "git.arvados.org/arvados.git/sdk/go/arvados"
23         "git.arvados.org/arvados.git/sdk/go/auth"
24 )
25
26 type TokenProvider func(context.Context) ([]string, error)
27
28 func PassthroughTokenProvider(ctx context.Context) ([]string, error) {
29         if incoming, ok := auth.FromContext(ctx); !ok {
30                 return nil, errors.New("no token provided")
31         }
32         return incoming.Tokens, nil
33 }
34
35 type Conn struct {
36         SendHeader    http.Header
37         clusterID     string
38         httpClient    http.Client
39         baseURL       url.URL
40         tokenProvider TokenProvider
41 }
42
43 func NewConn(clusterID string, url *url.URL, insecure bool, tp TokenProvider) *Conn {
44         transport := http.DefaultTransport
45         if insecure {
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,
56                                 DualStack: true,
57                         }).DialContext,
58                         MaxIdleConns:          100,
59                         IdleConnTimeout:       90 * time.Second,
60                         TLSHandshakeTimeout:   10 * time.Second,
61                         ExpectContinueTimeout: 1 * time.Second,
62                         TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
63                 }
64         }
65         return &Conn{
66                 clusterID: clusterID,
67                 httpClient: http.Client{
68                         CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
69                         Transport:     transport,
70                 },
71                 baseURL:       *url,
72                 tokenProvider: tp,
73         }
74 }
75
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,
82         }
83         tokens, err := conn.tokenProvider(ctx)
84         if err != nil {
85                 return err
86         } else if len(tokens) > 0 {
87                 ctx = arvados.ContextWithAuthorization(ctx, "Bearer "+tokens[0])
88         } else {
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
92                 // responding 401.
93                 ctx = arvados.ContextWithAuthorization(ctx, "Bearer -")
94         }
95
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)
100         if err != nil {
101                 return fmt.Errorf("%T: requestAndDecode: Marshal opts: %s", conn, err)
102         }
103         var params map[string]interface{}
104         dec := json.NewDecoder(bytes.NewBuffer(j))
105         dec.UseNumber()
106         err = dec.Decode(&params)
107         if err != nil {
108                 return fmt.Errorf("%T: requestAndDecode: Decode opts: %s", conn, err)
109         }
110         if attrs, ok := params["attrs"]; ok && ep.AttrsKey != "" {
111                 params[ep.AttrsKey] = attrs
112                 delete(params, "attrs")
113         }
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")
120                 }
121         }
122         if len(tokens) > 1 {
123                 params["reader_tokens"] = tokens[1:]
124         }
125         path := ep.Path
126         if strings.Contains(ep.Path, "/{uuid}") {
127                 uuid, _ := params["uuid"].(string)
128                 path = strings.Replace(path, "/{uuid}", "/"+uuid, 1)
129                 delete(params, "uuid")
130         }
131         return aClient.RequestAndDecodeContext(ctx, dst, ep.Method, path, body, params)
132 }
133
134 func (conn *Conn) BaseURL() url.URL {
135         return conn.baseURL
136 }
137
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)
142         return resp, err
143 }
144
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)
150         return resp, err
151 }
152
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)
158         return resp, err
159 }
160
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
163 // unmodified.
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) {
167                 u.Opaque = ""
168                 u.Scheme = ""
169                 u.User = nil
170                 u.Host = ""
171                 return u.String()
172         }
173         return location
174 }
175
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)
180         return resp, err
181 }
182
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)
187         return resp, err
188 }
189
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)
194         return resp, err
195 }
196
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)
201         return resp, err
202 }
203
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)
208         return resp, err
209 }
210
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)
215         return resp, err
216 }
217
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)
222         return resp, err
223 }
224
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)
229         return resp, err
230 }
231
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)
236         return resp, err
237 }
238
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)
243         return resp, err
244 }
245
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)
250         return resp, err
251 }
252
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)
257         return resp, err
258 }
259
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)
264         return resp, err
265 }
266
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)
271         return resp, err
272 }
273
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)
278         return resp, err
279 }
280
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)
285         return resp, err
286 }
287
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)
292         return resp, err
293 }
294
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)
299         return resp, err
300 }
301
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)
306         return resp, err
307 }
308
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)
313         return resp, err
314 }
315
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)
320         return resp, err
321 }
322
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)
327         return resp, err
328 }
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)
333         return resp, err
334 }
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)
339         return resp, err
340 }
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)
345         return resp, err
346 }
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)
351         return resp, err
352 }
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)
357         return resp, err
358 }
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)
363         return resp, err
364 }
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)
369         return resp, err
370 }
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)
375         return resp, err
376 }
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)
381         return resp, err
382 }
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)
387         return resp, err
388 }
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)
393         return resp, err
394 }
395
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)
400         return resp, err
401 }
402
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"`
409 }
410
411 type UserSessionCreateOptions struct {
412         AuthInfo UserSessionAuthInfo `json:"auth_info"`
413         ReturnTo string              `json:"return_to"`
414 }
415
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)
420         return resp, err
421 }
422
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)
427         return resp, err
428 }
429
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)
434         return resp, err
435 }