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