]> git.arvados.org - arvados.git/blob - lib/controller/router/router.go
22321: Add Marshal and RoundTrip tests for BlockSegment.
[arvados.git] / lib / controller / router / router.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package router
6
7 import (
8         "context"
9         "fmt"
10         "math"
11         "net/http"
12         "strings"
13
14         "git.arvados.org/arvados.git/lib/controller/api"
15         "git.arvados.org/arvados.git/sdk/go/arvados"
16         "git.arvados.org/arvados.git/sdk/go/arvadosclient"
17         "git.arvados.org/arvados.git/sdk/go/auth"
18         "git.arvados.org/arvados.git/sdk/go/ctxlog"
19         "git.arvados.org/arvados.git/sdk/go/httpserver"
20         "github.com/gorilla/mux"
21         "github.com/sirupsen/logrus"
22 )
23
24 type router struct {
25         mux     *mux.Router
26         backend arvados.API
27         config  Config
28 }
29
30 type Config struct {
31         // Return an error if request body exceeds this size. 0 means
32         // unlimited.
33         MaxRequestSize int
34
35         // If wrapCalls is not nil, it is called once for each API
36         // method, and the returned method is used in its place. This
37         // can be used to install hooks before and after each API call
38         // and alter responses; see localdb.WrapCallsInTransaction for
39         // an example.
40         WrapCalls func(api.RoutableFunc) api.RoutableFunc
41 }
42
43 // New returns a new router (which implements the http.Handler
44 // interface) that serves requests by calling Arvados API methods on
45 // the given backend.
46 func New(backend arvados.API, config Config) *router {
47         rtr := &router{
48                 mux:     mux.NewRouter(),
49                 backend: backend,
50                 config:  config,
51         }
52         rtr.addRoutes()
53         return rtr
54 }
55
56 func (rtr *router) addRoutes() {
57         for _, route := range []struct {
58                 endpoint    arvados.APIEndpoint
59                 defaultOpts func() interface{}
60                 exec        api.RoutableFunc
61         }{
62                 {
63                         arvados.EndpointConfigGet,
64                         func() interface{} { return &struct{}{} },
65                         func(ctx context.Context, opts interface{}) (interface{}, error) {
66                                 return rtr.backend.ConfigGet(ctx)
67                         },
68                 },
69                 {
70                         arvados.EndpointVocabularyGet,
71                         func() interface{} { return &struct{}{} },
72                         func(ctx context.Context, opts interface{}) (interface{}, error) {
73                                 return rtr.backend.VocabularyGet(ctx)
74                         },
75                 },
76                 {
77                         arvados.EndpointLogin,
78                         func() interface{} { return &arvados.LoginOptions{} },
79                         func(ctx context.Context, opts interface{}) (interface{}, error) {
80                                 return rtr.backend.Login(ctx, *opts.(*arvados.LoginOptions))
81                         },
82                 },
83                 {
84                         arvados.EndpointLogout,
85                         func() interface{} { return &arvados.LogoutOptions{} },
86                         func(ctx context.Context, opts interface{}) (interface{}, error) {
87                                 return rtr.backend.Logout(ctx, *opts.(*arvados.LogoutOptions))
88                         },
89                 },
90                 {
91                         arvados.EndpointAuthorizedKeyCreate,
92                         func() interface{} { return &arvados.CreateOptions{} },
93                         func(ctx context.Context, opts interface{}) (interface{}, error) {
94                                 return rtr.backend.AuthorizedKeyCreate(ctx, *opts.(*arvados.CreateOptions))
95                         },
96                 },
97                 {
98                         arvados.EndpointAuthorizedKeyUpdate,
99                         func() interface{} { return &arvados.UpdateOptions{} },
100                         func(ctx context.Context, opts interface{}) (interface{}, error) {
101                                 return rtr.backend.AuthorizedKeyUpdate(ctx, *opts.(*arvados.UpdateOptions))
102                         },
103                 },
104                 {
105                         arvados.EndpointAuthorizedKeyGet,
106                         func() interface{} { return &arvados.GetOptions{} },
107                         func(ctx context.Context, opts interface{}) (interface{}, error) {
108                                 return rtr.backend.AuthorizedKeyGet(ctx, *opts.(*arvados.GetOptions))
109                         },
110                 },
111                 {
112                         arvados.EndpointAuthorizedKeyList,
113                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
114                         func(ctx context.Context, opts interface{}) (interface{}, error) {
115                                 return rtr.backend.AuthorizedKeyList(ctx, *opts.(*arvados.ListOptions))
116                         },
117                 },
118                 {
119                         arvados.EndpointAuthorizedKeyDelete,
120                         func() interface{} { return &arvados.DeleteOptions{} },
121                         func(ctx context.Context, opts interface{}) (interface{}, error) {
122                                 return rtr.backend.AuthorizedKeyDelete(ctx, *opts.(*arvados.DeleteOptions))
123                         },
124                 },
125                 {
126                         arvados.EndpointCollectionCreate,
127                         func() interface{} { return &arvados.CreateOptions{} },
128                         func(ctx context.Context, opts interface{}) (interface{}, error) {
129                                 return rtr.backend.CollectionCreate(ctx, *opts.(*arvados.CreateOptions))
130                         },
131                 },
132                 {
133                         arvados.EndpointCollectionUpdate,
134                         func() interface{} { return &arvados.UpdateOptions{} },
135                         func(ctx context.Context, opts interface{}) (interface{}, error) {
136                                 return rtr.backend.CollectionUpdate(ctx, *opts.(*arvados.UpdateOptions))
137                         },
138                 },
139                 {
140                         arvados.EndpointCollectionGet,
141                         func() interface{} { return &arvados.GetOptions{} },
142                         func(ctx context.Context, opts interface{}) (interface{}, error) {
143                                 return rtr.backend.CollectionGet(ctx, *opts.(*arvados.GetOptions))
144                         },
145                 },
146                 {
147                         arvados.EndpointCollectionList,
148                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
149                         func(ctx context.Context, opts interface{}) (interface{}, error) {
150                                 return rtr.backend.CollectionList(ctx, *opts.(*arvados.ListOptions))
151                         },
152                 },
153                 {
154                         arvados.EndpointCollectionProvenance,
155                         func() interface{} { return &arvados.GetOptions{} },
156                         func(ctx context.Context, opts interface{}) (interface{}, error) {
157                                 return rtr.backend.CollectionProvenance(ctx, *opts.(*arvados.GetOptions))
158                         },
159                 },
160                 {
161                         arvados.EndpointCollectionUsedBy,
162                         func() interface{} { return &arvados.GetOptions{} },
163                         func(ctx context.Context, opts interface{}) (interface{}, error) {
164                                 return rtr.backend.CollectionUsedBy(ctx, *opts.(*arvados.GetOptions))
165                         },
166                 },
167                 {
168                         arvados.EndpointCollectionDelete,
169                         func() interface{} { return &arvados.DeleteOptions{} },
170                         func(ctx context.Context, opts interface{}) (interface{}, error) {
171                                 return rtr.backend.CollectionDelete(ctx, *opts.(*arvados.DeleteOptions))
172                         },
173                 },
174                 {
175                         arvados.EndpointCollectionTrash,
176                         func() interface{} { return &arvados.DeleteOptions{} },
177                         func(ctx context.Context, opts interface{}) (interface{}, error) {
178                                 return rtr.backend.CollectionTrash(ctx, *opts.(*arvados.DeleteOptions))
179                         },
180                 },
181                 {
182                         arvados.EndpointCollectionUntrash,
183                         func() interface{} { return &arvados.UntrashOptions{} },
184                         func(ctx context.Context, opts interface{}) (interface{}, error) {
185                                 return rtr.backend.CollectionUntrash(ctx, *opts.(*arvados.UntrashOptions))
186                         },
187                 },
188                 {
189                         arvados.EndpointComputedPermissionList,
190                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
191                         func(ctx context.Context, opts interface{}) (interface{}, error) {
192                                 return rtr.backend.ComputedPermissionList(ctx, *opts.(*arvados.ListOptions))
193                         },
194                 },
195                 {
196                         arvados.EndpointContainerCreate,
197                         func() interface{} { return &arvados.CreateOptions{} },
198                         func(ctx context.Context, opts interface{}) (interface{}, error) {
199                                 return rtr.backend.ContainerCreate(ctx, *opts.(*arvados.CreateOptions))
200                         },
201                 },
202                 {
203                         arvados.EndpointContainerPriorityUpdate,
204                         func() interface{} { return &arvados.UpdateOptions{} },
205                         func(ctx context.Context, opts interface{}) (interface{}, error) {
206                                 return rtr.backend.ContainerPriorityUpdate(ctx, *opts.(*arvados.UpdateOptions))
207                         },
208                 },
209                 {
210                         arvados.EndpointContainerUpdate,
211                         func() interface{} { return &arvados.UpdateOptions{} },
212                         func(ctx context.Context, opts interface{}) (interface{}, error) {
213                                 return rtr.backend.ContainerUpdate(ctx, *opts.(*arvados.UpdateOptions))
214                         },
215                 },
216                 {
217                         arvados.EndpointContainerGet,
218                         func() interface{} { return &arvados.GetOptions{} },
219                         func(ctx context.Context, opts interface{}) (interface{}, error) {
220                                 return rtr.backend.ContainerGet(ctx, *opts.(*arvados.GetOptions))
221                         },
222                 },
223                 {
224                         arvados.EndpointContainerList,
225                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
226                         func(ctx context.Context, opts interface{}) (interface{}, error) {
227                                 return rtr.backend.ContainerList(ctx, *opts.(*arvados.ListOptions))
228                         },
229                 },
230                 {
231                         arvados.EndpointContainerDelete,
232                         func() interface{} { return &arvados.DeleteOptions{} },
233                         func(ctx context.Context, opts interface{}) (interface{}, error) {
234                                 return rtr.backend.ContainerDelete(ctx, *opts.(*arvados.DeleteOptions))
235                         },
236                 },
237                 {
238                         arvados.EndpointContainerLock,
239                         func() interface{} {
240                                 return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
241                         },
242                         func(ctx context.Context, opts interface{}) (interface{}, error) {
243                                 return rtr.backend.ContainerLock(ctx, *opts.(*arvados.GetOptions))
244                         },
245                 },
246                 {
247                         arvados.EndpointContainerUnlock,
248                         func() interface{} {
249                                 return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
250                         },
251                         func(ctx context.Context, opts interface{}) (interface{}, error) {
252                                 return rtr.backend.ContainerUnlock(ctx, *opts.(*arvados.GetOptions))
253                         },
254                 },
255                 {
256                         arvados.EndpointContainerSSH,
257                         func() interface{} { return &arvados.ContainerSSHOptions{} },
258                         func(ctx context.Context, opts interface{}) (interface{}, error) {
259                                 return rtr.backend.ContainerSSH(ctx, *opts.(*arvados.ContainerSSHOptions))
260                         },
261                 },
262                 {
263                         arvados.EndpointContainerSSHCompat,
264                         func() interface{} { return &arvados.ContainerSSHOptions{} },
265                         func(ctx context.Context, opts interface{}) (interface{}, error) {
266                                 return rtr.backend.ContainerSSH(ctx, *opts.(*arvados.ContainerSSHOptions))
267                         },
268                 },
269                 {
270                         // arvados-client built before commit
271                         // bdc29d3129f6d75aa9ce0a24ffb849a272b06f08
272                         // used GET with params in headers instead of
273                         // POST form
274                         arvados.APIEndpoint{"GET", "arvados/v1/connect/{uuid}/ssh", ""},
275                         func() interface{} { return &arvados.ContainerSSHOptions{} },
276                         func(ctx context.Context, opts interface{}) (interface{}, error) {
277                                 return nil, httpError(http.StatusGone, fmt.Errorf("API endpoint is obsolete -- please upgrade your arvados-client program"))
278                         },
279                 },
280                 {
281                         arvados.EndpointContainerGatewayTunnel,
282                         func() interface{} { return &arvados.ContainerGatewayTunnelOptions{} },
283                         func(ctx context.Context, opts interface{}) (interface{}, error) {
284                                 return rtr.backend.ContainerGatewayTunnel(ctx, *opts.(*arvados.ContainerGatewayTunnelOptions))
285                         },
286                 },
287                 {
288                         arvados.EndpointContainerGatewayTunnelCompat,
289                         func() interface{} { return &arvados.ContainerGatewayTunnelOptions{} },
290                         func(ctx context.Context, opts interface{}) (interface{}, error) {
291                                 return rtr.backend.ContainerGatewayTunnel(ctx, *opts.(*arvados.ContainerGatewayTunnelOptions))
292                         },
293                 },
294                 {
295                         arvados.EndpointContainerRequestCreate,
296                         func() interface{} { return &arvados.CreateOptions{} },
297                         func(ctx context.Context, opts interface{}) (interface{}, error) {
298                                 return rtr.backend.ContainerRequestCreate(ctx, *opts.(*arvados.CreateOptions))
299                         },
300                 },
301                 {
302                         arvados.EndpointContainerRequestUpdate,
303                         func() interface{} { return &arvados.UpdateOptions{} },
304                         func(ctx context.Context, opts interface{}) (interface{}, error) {
305                                 return rtr.backend.ContainerRequestUpdate(ctx, *opts.(*arvados.UpdateOptions))
306                         },
307                 },
308                 {
309                         arvados.EndpointContainerRequestGet,
310                         func() interface{} { return &arvados.GetOptions{} },
311                         func(ctx context.Context, opts interface{}) (interface{}, error) {
312                                 return rtr.backend.ContainerRequestGet(ctx, *opts.(*arvados.GetOptions))
313                         },
314                 },
315                 {
316                         arvados.EndpointContainerRequestList,
317                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
318                         func(ctx context.Context, opts interface{}) (interface{}, error) {
319                                 return rtr.backend.ContainerRequestList(ctx, *opts.(*arvados.ListOptions))
320                         },
321                 },
322                 {
323                         arvados.EndpointContainerRequestDelete,
324                         func() interface{} { return &arvados.DeleteOptions{} },
325                         func(ctx context.Context, opts interface{}) (interface{}, error) {
326                                 return rtr.backend.ContainerRequestDelete(ctx, *opts.(*arvados.DeleteOptions))
327                         },
328                 },
329                 {
330                         arvados.EndpointContainerRequestContainerStatus,
331                         func() interface{} { return &arvados.GetOptions{} },
332                         func(ctx context.Context, opts interface{}) (interface{}, error) {
333                                 return rtr.backend.ContainerRequestContainerStatus(ctx, *opts.(*arvados.GetOptions))
334                         },
335                 },
336                 {
337                         arvados.EndpointContainerRequestLog,
338                         func() interface{} { return &arvados.ContainerLogOptions{} },
339                         func(ctx context.Context, opts interface{}) (interface{}, error) {
340                                 return rtr.backend.ContainerRequestLog(ctx, *opts.(*arvados.ContainerLogOptions))
341                         },
342                 },
343                 {
344                         arvados.EndpointGroupCreate,
345                         func() interface{} { return &arvados.CreateOptions{} },
346                         func(ctx context.Context, opts interface{}) (interface{}, error) {
347                                 return rtr.backend.GroupCreate(ctx, *opts.(*arvados.CreateOptions))
348                         },
349                 },
350                 {
351                         arvados.EndpointGroupUpdate,
352                         func() interface{} { return &arvados.UpdateOptions{} },
353                         func(ctx context.Context, opts interface{}) (interface{}, error) {
354                                 return rtr.backend.GroupUpdate(ctx, *opts.(*arvados.UpdateOptions))
355                         },
356                 },
357                 {
358                         arvados.EndpointGroupList,
359                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
360                         func(ctx context.Context, opts interface{}) (interface{}, error) {
361                                 return rtr.backend.GroupList(ctx, *opts.(*arvados.ListOptions))
362                         },
363                 },
364                 {
365                         arvados.EndpointGroupContents,
366                         func() interface{} { return &arvados.GroupContentsOptions{Limit: -1} },
367                         func(ctx context.Context, opts interface{}) (interface{}, error) {
368                                 return rtr.backend.GroupContents(ctx, *opts.(*arvados.GroupContentsOptions))
369                         },
370                 },
371                 {
372                         arvados.EndpointGroupContentsUUIDInPath,
373                         func() interface{} { return &arvados.GroupContentsOptions{Limit: -1} },
374                         func(ctx context.Context, opts interface{}) (interface{}, error) {
375                                 return rtr.backend.GroupContents(ctx, *opts.(*arvados.GroupContentsOptions))
376                         },
377                 },
378                 {
379                         arvados.EndpointGroupShared,
380                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
381                         func(ctx context.Context, opts interface{}) (interface{}, error) {
382                                 return rtr.backend.GroupShared(ctx, *opts.(*arvados.ListOptions))
383                         },
384                 },
385                 {
386                         arvados.EndpointGroupGet,
387                         func() interface{} { return &arvados.GetOptions{} },
388                         func(ctx context.Context, opts interface{}) (interface{}, error) {
389                                 return rtr.backend.GroupGet(ctx, *opts.(*arvados.GetOptions))
390                         },
391                 },
392                 {
393                         arvados.EndpointGroupDelete,
394                         func() interface{} { return &arvados.DeleteOptions{} },
395                         func(ctx context.Context, opts interface{}) (interface{}, error) {
396                                 return rtr.backend.GroupDelete(ctx, *opts.(*arvados.DeleteOptions))
397                         },
398                 },
399                 {
400                         arvados.EndpointGroupTrash,
401                         func() interface{} { return &arvados.DeleteOptions{} },
402                         func(ctx context.Context, opts interface{}) (interface{}, error) {
403                                 return rtr.backend.GroupTrash(ctx, *opts.(*arvados.DeleteOptions))
404                         },
405                 },
406                 {
407                         arvados.EndpointGroupUntrash,
408                         func() interface{} { return &arvados.UntrashOptions{} },
409                         func(ctx context.Context, opts interface{}) (interface{}, error) {
410                                 return rtr.backend.GroupUntrash(ctx, *opts.(*arvados.UntrashOptions))
411                         },
412                 },
413                 {
414                         arvados.EndpointLinkCreate,
415                         func() interface{} { return &arvados.CreateOptions{} },
416                         func(ctx context.Context, opts interface{}) (interface{}, error) {
417                                 return rtr.backend.LinkCreate(ctx, *opts.(*arvados.CreateOptions))
418                         },
419                 },
420                 {
421                         arvados.EndpointLinkUpdate,
422                         func() interface{} { return &arvados.UpdateOptions{} },
423                         func(ctx context.Context, opts interface{}) (interface{}, error) {
424                                 return rtr.backend.LinkUpdate(ctx, *opts.(*arvados.UpdateOptions))
425                         },
426                 },
427                 {
428                         arvados.EndpointLinkList,
429                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
430                         func(ctx context.Context, opts interface{}) (interface{}, error) {
431                                 return rtr.backend.LinkList(ctx, *opts.(*arvados.ListOptions))
432                         },
433                 },
434                 {
435                         arvados.EndpointLinkGet,
436                         func() interface{} { return &arvados.GetOptions{} },
437                         func(ctx context.Context, opts interface{}) (interface{}, error) {
438                                 return rtr.backend.LinkGet(ctx, *opts.(*arvados.GetOptions))
439                         },
440                 },
441                 {
442                         arvados.EndpointLinkDelete,
443                         func() interface{} { return &arvados.DeleteOptions{} },
444                         func(ctx context.Context, opts interface{}) (interface{}, error) {
445                                 return rtr.backend.LinkDelete(ctx, *opts.(*arvados.DeleteOptions))
446                         },
447                 },
448                 {
449                         arvados.EndpointLogCreate,
450                         func() interface{} { return &arvados.CreateOptions{} },
451                         func(ctx context.Context, opts interface{}) (interface{}, error) {
452                                 return rtr.backend.LogCreate(ctx, *opts.(*arvados.CreateOptions))
453                         },
454                 },
455                 {
456                         arvados.EndpointLogUpdate,
457                         func() interface{} { return &arvados.UpdateOptions{} },
458                         func(ctx context.Context, opts interface{}) (interface{}, error) {
459                                 return rtr.backend.LogUpdate(ctx, *opts.(*arvados.UpdateOptions))
460                         },
461                 },
462                 {
463                         arvados.EndpointLogList,
464                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
465                         func(ctx context.Context, opts interface{}) (interface{}, error) {
466                                 return rtr.backend.LogList(ctx, *opts.(*arvados.ListOptions))
467                         },
468                 },
469                 {
470                         arvados.EndpointLogGet,
471                         func() interface{} { return &arvados.GetOptions{} },
472                         func(ctx context.Context, opts interface{}) (interface{}, error) {
473                                 return rtr.backend.LogGet(ctx, *opts.(*arvados.GetOptions))
474                         },
475                 },
476                 {
477                         arvados.EndpointLogDelete,
478                         func() interface{} { return &arvados.DeleteOptions{} },
479                         func(ctx context.Context, opts interface{}) (interface{}, error) {
480                                 return rtr.backend.LogDelete(ctx, *opts.(*arvados.DeleteOptions))
481                         },
482                 },
483                 {
484                         arvados.EndpointAPIClientAuthorizationCreate,
485                         func() interface{} { return &arvados.CreateOptions{} },
486                         func(ctx context.Context, opts interface{}) (interface{}, error) {
487                                 return rtr.backend.APIClientAuthorizationCreate(ctx, *opts.(*arvados.CreateOptions))
488                         },
489                 },
490                 {
491                         arvados.EndpointAPIClientAuthorizationUpdate,
492                         func() interface{} { return &arvados.UpdateOptions{} },
493                         func(ctx context.Context, opts interface{}) (interface{}, error) {
494                                 return rtr.backend.APIClientAuthorizationUpdate(ctx, *opts.(*arvados.UpdateOptions))
495                         },
496                 },
497                 {
498                         arvados.EndpointAPIClientAuthorizationDelete,
499                         func() interface{} { return &arvados.DeleteOptions{} },
500                         func(ctx context.Context, opts interface{}) (interface{}, error) {
501                                 return rtr.backend.APIClientAuthorizationDelete(ctx, *opts.(*arvados.DeleteOptions))
502                         },
503                 },
504                 {
505                         arvados.EndpointAPIClientAuthorizationList,
506                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
507                         func(ctx context.Context, opts interface{}) (interface{}, error) {
508                                 return rtr.backend.APIClientAuthorizationList(ctx, *opts.(*arvados.ListOptions))
509                         },
510                 },
511                 {
512                         arvados.EndpointAPIClientAuthorizationCurrent,
513                         func() interface{} { return &arvados.GetOptions{} },
514                         func(ctx context.Context, opts interface{}) (interface{}, error) {
515                                 return rtr.backend.APIClientAuthorizationCurrent(ctx, *opts.(*arvados.GetOptions))
516                         },
517                 },
518                 {
519                         arvados.EndpointAPIClientAuthorizationGet,
520                         func() interface{} { return &arvados.GetOptions{} },
521                         func(ctx context.Context, opts interface{}) (interface{}, error) {
522                                 return rtr.backend.APIClientAuthorizationGet(ctx, *opts.(*arvados.GetOptions))
523                         },
524                 },
525                 {
526                         arvados.EndpointUserCreate,
527                         func() interface{} { return &arvados.CreateOptions{} },
528                         func(ctx context.Context, opts interface{}) (interface{}, error) {
529                                 return rtr.backend.UserCreate(ctx, *opts.(*arvados.CreateOptions))
530                         },
531                 },
532                 {
533                         arvados.EndpointUserMerge,
534                         func() interface{} { return &arvados.UserMergeOptions{} },
535                         func(ctx context.Context, opts interface{}) (interface{}, error) {
536                                 return rtr.backend.UserMerge(ctx, *opts.(*arvados.UserMergeOptions))
537                         },
538                 },
539                 {
540                         arvados.EndpointUserActivate,
541                         func() interface{} { return &arvados.UserActivateOptions{} },
542                         func(ctx context.Context, opts interface{}) (interface{}, error) {
543                                 return rtr.backend.UserActivate(ctx, *opts.(*arvados.UserActivateOptions))
544                         },
545                 },
546                 {
547                         arvados.EndpointUserSetup,
548                         func() interface{} { return &arvados.UserSetupOptions{} },
549                         func(ctx context.Context, opts interface{}) (interface{}, error) {
550                                 return rtr.backend.UserSetup(ctx, *opts.(*arvados.UserSetupOptions))
551                         },
552                 },
553                 {
554                         arvados.EndpointUserUnsetup,
555                         func() interface{} { return &arvados.GetOptions{} },
556                         func(ctx context.Context, opts interface{}) (interface{}, error) {
557                                 return rtr.backend.UserUnsetup(ctx, *opts.(*arvados.GetOptions))
558                         },
559                 },
560                 {
561                         arvados.EndpointUserGetCurrent,
562                         func() interface{} { return &arvados.GetOptions{} },
563                         func(ctx context.Context, opts interface{}) (interface{}, error) {
564                                 return rtr.backend.UserGetCurrent(ctx, *opts.(*arvados.GetOptions))
565                         },
566                 },
567                 {
568                         arvados.EndpointUserGetSystem,
569                         func() interface{} { return &arvados.GetOptions{} },
570                         func(ctx context.Context, opts interface{}) (interface{}, error) {
571                                 return rtr.backend.UserGetSystem(ctx, *opts.(*arvados.GetOptions))
572                         },
573                 },
574                 {
575                         arvados.EndpointUserGet,
576                         func() interface{} { return &arvados.GetOptions{} },
577                         func(ctx context.Context, opts interface{}) (interface{}, error) {
578                                 return rtr.backend.UserGet(ctx, *opts.(*arvados.GetOptions))
579                         },
580                 },
581                 {
582                         arvados.EndpointUserUpdate,
583                         func() interface{} { return &arvados.UpdateOptions{} },
584                         func(ctx context.Context, opts interface{}) (interface{}, error) {
585                                 return rtr.backend.UserUpdate(ctx, *opts.(*arvados.UpdateOptions))
586                         },
587                 },
588                 {
589                         arvados.EndpointUserList,
590                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
591                         func(ctx context.Context, opts interface{}) (interface{}, error) {
592                                 return rtr.backend.UserList(ctx, *opts.(*arvados.ListOptions))
593                         },
594                 },
595                 {
596                         arvados.EndpointUserBatchUpdate,
597                         func() interface{} { return &arvados.UserBatchUpdateOptions{} },
598                         func(ctx context.Context, opts interface{}) (interface{}, error) {
599                                 return rtr.backend.UserBatchUpdate(ctx, *opts.(*arvados.UserBatchUpdateOptions))
600                         },
601                 },
602                 {
603                         arvados.EndpointUserDelete,
604                         func() interface{} { return &arvados.DeleteOptions{} },
605                         func(ctx context.Context, opts interface{}) (interface{}, error) {
606                                 return rtr.backend.UserDelete(ctx, *opts.(*arvados.DeleteOptions))
607                         },
608                 },
609                 {
610                         arvados.EndpointUserAuthenticate,
611                         func() interface{} { return &arvados.UserAuthenticateOptions{} },
612                         func(ctx context.Context, opts interface{}) (interface{}, error) {
613                                 return rtr.backend.UserAuthenticate(ctx, *opts.(*arvados.UserAuthenticateOptions))
614                         },
615                 },
616         } {
617                 exec := route.exec
618                 if rtr.config.WrapCalls != nil {
619                         exec = rtr.config.WrapCalls(exec)
620                 }
621                 rtr.addRoute(route.endpoint, route.defaultOpts, exec)
622         }
623         rtr.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
624                 if req.Method == "OPTIONS" {
625                         // For non-webdav endpoints, return an empty
626                         // response with the CORS headers we already
627                         // added in ServeHTTP.
628                         w.WriteHeader(http.StatusOK)
629                         return
630                 }
631                 httpserver.Errors(w, []string{"API endpoint not found"}, http.StatusNotFound)
632         })
633         rtr.mux.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
634                 if req.Method == "OPTIONS" {
635                         // For non-webdav endpoints, return an empty
636                         // response with the CORS headers we already
637                         // added in ServeHTTP.
638                         w.WriteHeader(http.StatusOK)
639                         return
640                 }
641                 httpserver.Errors(w, []string{"API endpoint not found"}, http.StatusMethodNotAllowed)
642         })
643 }
644
645 var altMethod = map[string]string{
646         "PATCH": "PUT",  // Accept PUT as a synonym for PATCH
647         "GET":   "HEAD", // Accept HEAD at any GET route
648 }
649
650 func (rtr *router) addRoute(endpoint arvados.APIEndpoint, defaultOpts func() interface{}, exec api.RoutableFunc) {
651         methods := []string{endpoint.Method}
652         if alt, ok := altMethod[endpoint.Method]; ok {
653                 methods = append(methods, alt)
654         }
655         if strings.HasSuffix(endpoint.Path, ".*}") {
656                 // webdav methods
657                 methods = append(methods, "OPTIONS", "PROPFIND")
658         }
659         rtr.mux.Methods(methods...).Path("/" + endpoint.Path).HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
660                 logger := ctxlog.FromContext(req.Context())
661                 opts := defaultOpts()
662                 params, err := rtr.loadRequestParams(req, endpoint.AttrsKey, opts)
663                 if err != nil {
664                         logger.WithFields(logrus.Fields{
665                                 "req":      req,
666                                 "method":   endpoint.Method,
667                                 "endpoint": endpoint,
668                         }).WithError(err).Debug("error loading request params")
669                         rtr.sendError(w, err)
670                         return
671                 }
672                 respOpts, err := rtr.responseOptions(opts)
673                 if err != nil {
674                         logger.WithField("opts", opts).WithError(err).Debugf("error getting response options from %T", opts)
675                         rtr.sendError(w, err)
676                         return
677                 }
678
679                 creds := auth.CredentialsFromRequest(req)
680                 err = creds.LoadTokensFromHTTPRequestBody(req)
681                 if err != nil {
682                         rtr.sendError(w, fmt.Errorf("error loading tokens from request body: %s", err))
683                         return
684                 }
685                 if rt, _ := params["reader_tokens"].([]interface{}); len(rt) > 0 {
686                         for _, t := range rt {
687                                 if t, ok := t.(string); ok {
688                                         creds.Tokens = append(creds.Tokens, t)
689                                 }
690                         }
691                 }
692                 ctx := auth.NewContext(req.Context(), creds)
693                 ctx = arvados.ContextWithRequestID(ctx, req.Header.Get("X-Request-Id"))
694                 req = req.WithContext(ctx)
695
696                 httpserver.SetResponseLogFields(ctx, logrus.Fields{"tokenUUIDs": creds.TokenUUIDs()})
697
698                 logger.WithFields(logrus.Fields{
699                         "apiEndpoint": endpoint,
700                         "apiOptsType": fmt.Sprintf("%T", opts),
701                         "apiOpts":     opts,
702                 }).Debug("exec")
703                 resp, err := exec(ctx, opts)
704                 if err != nil {
705                         logger.WithError(err).Debugf("returning error type %T", err)
706                         rtr.sendError(w, err)
707                         return
708                 }
709                 rtr.sendResponse(w, req, resp, respOpts)
710         })
711 }
712
713 func (rtr *router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
714         if len(r.Host) > 28 && arvadosclient.UUIDMatch(r.Host[:27]) && r.Host[27] == '-' {
715                 var port int
716                 fmt.Sscanf(r.Host[28:], "%d", &port)
717                 if port < 1 {
718                         rtr.sendError(w, httpError(http.StatusBadRequest, fmt.Errorf("cannot parse port number from vhost %q", r.Host)))
719                         return
720                 }
721                 rtr.serveContainerHTTPProxy(w, r, r.Host[:27], port)
722                 return
723         }
724         switch strings.SplitN(strings.TrimLeft(r.URL.Path, "/"), "/", 2)[0] {
725         case "login", "logout", "auth":
726         default:
727                 w.Header().Set("Access-Control-Allow-Origin", "*")
728                 w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS, PROPFIND, PUT, POST, PATCH, DELETE")
729                 w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, Range, X-Http-Method-Override")
730                 w.Header().Set("Access-Control-Expose-Headers", "Content-Range")
731                 w.Header().Set("Access-Control-Max-Age", "86486400")
732         }
733         if r.Body != nil {
734                 // Wrap r.Body in a http.MaxBytesReader(), otherwise
735                 // r.ParseForm() uses a default max request body size
736                 // of 10 megabytes. Note we rely on the Nginx
737                 // configuration to enforce the real max body size.
738                 max := int64(rtr.config.MaxRequestSize)
739                 if max < 1 {
740                         max = math.MaxInt64 - 1
741                 }
742                 r.Body = http.MaxBytesReader(w, r.Body, max)
743         }
744         if r.Method == "POST" {
745                 err := r.ParseForm()
746                 if err != nil {
747                         if err.Error() == "http: request body too large" {
748                                 err = httpError(http.StatusRequestEntityTooLarge, err)
749                         }
750                         rtr.sendError(w, err)
751                         return
752                 }
753                 if m := r.FormValue("_method"); m != "" {
754                         r2 := *r
755                         r = &r2
756                         r.Method = m
757                 } else if m = r.Header.Get("X-Http-Method-Override"); m != "" {
758                         r2 := *r
759                         r = &r2
760                         r.Method = m
761                 }
762         }
763         rtr.mux.ServeHTTP(w, r)
764 }
765
766 func (rtr *router) serveContainerHTTPProxy(w http.ResponseWriter, req *http.Request, uuid string, port int) {
767         // This API bypasses the generic auth middleware in
768         // addRoute(), so here we need to load tokens into ctx, log
769         // their UUIDs, and propagate the incoming X-Request-Id.
770         ctx := req.Context()
771         if cookie, err := req.Cookie("arvados_api_token"); err == nil && len(cookie.Value) != 0 {
772                 if token, err := auth.DecodeTokenCookie(cookie.Value); err == nil {
773                         creds := auth.NewCredentials(string(token))
774                         ctx = auth.NewContext(ctx, creds)
775                         httpserver.SetResponseLogFields(ctx, logrus.Fields{"tokenUUIDs": creds.TokenUUIDs()})
776                 }
777         }
778
779         ctx = arvados.ContextWithRequestID(ctx, req.Header.Get("X-Request-Id"))
780         req = req.WithContext(ctx)
781
782         // Load the NoForward value from the X-Arvados-No-Forward
783         // header, but don't pass the header through in the proxied
784         // request.
785         noForward := req.Header.Get("X-Arvados-No-Forward") != ""
786         req.Header.Del("X-Arvados-No-Forward")
787
788         handler, err := rtr.backend.ContainerHTTPProxy(req.Context(), arvados.ContainerHTTPProxyOptions{
789                 UUID:      uuid,
790                 Port:      port,
791                 Request:   req,
792                 NoForward: noForward,
793         })
794         if err != nil {
795                 rtr.sendError(w, err)
796                 return
797         }
798         handler.ServeHTTP(w, req)
799 }