21578: Merge branch 'main' into 21578-mount-debug
[arvados.git] / lib / controller / router / router.go
index 02e06279f1168adca61999a543a9e82ad059e424..054bcffaf7ecf33b12965bb8e0d0be2d9590e1e0 100644 (file)
@@ -86,6 +86,41 @@ func (rtr *router) addRoutes() {
                                return rtr.backend.Logout(ctx, *opts.(*arvados.LogoutOptions))
                        },
                },
+               {
+                       arvados.EndpointAuthorizedKeyCreate,
+                       func() interface{} { return &arvados.CreateOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.AuthorizedKeyCreate(ctx, *opts.(*arvados.CreateOptions))
+                       },
+               },
+               {
+                       arvados.EndpointAuthorizedKeyUpdate,
+                       func() interface{} { return &arvados.UpdateOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.AuthorizedKeyUpdate(ctx, *opts.(*arvados.UpdateOptions))
+                       },
+               },
+               {
+                       arvados.EndpointAuthorizedKeyGet,
+                       func() interface{} { return &arvados.GetOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.AuthorizedKeyGet(ctx, *opts.(*arvados.GetOptions))
+                       },
+               },
+               {
+                       arvados.EndpointAuthorizedKeyList,
+                       func() interface{} { return &arvados.ListOptions{Limit: -1} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.AuthorizedKeyList(ctx, *opts.(*arvados.ListOptions))
+                       },
+               },
+               {
+                       arvados.EndpointAuthorizedKeyDelete,
+                       func() interface{} { return &arvados.DeleteOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.AuthorizedKeyDelete(ctx, *opts.(*arvados.DeleteOptions))
+                       },
+               },
                {
                        arvados.EndpointCollectionCreate,
                        func() interface{} { return &arvados.CreateOptions{} },
@@ -156,6 +191,13 @@ func (rtr *router) addRoutes() {
                                return rtr.backend.ContainerCreate(ctx, *opts.(*arvados.CreateOptions))
                        },
                },
+               {
+                       arvados.EndpointContainerPriorityUpdate,
+                       func() interface{} { return &arvados.UpdateOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.ContainerPriorityUpdate(ctx, *opts.(*arvados.UpdateOptions))
+                       },
+               },
                {
                        arvados.EndpointContainerUpdate,
                        func() interface{} { return &arvados.UpdateOptions{} },
@@ -184,6 +226,63 @@ func (rtr *router) addRoutes() {
                                return rtr.backend.ContainerDelete(ctx, *opts.(*arvados.DeleteOptions))
                        },
                },
+               {
+                       arvados.EndpointContainerLock,
+                       func() interface{} {
+                               return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
+                       },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.ContainerLock(ctx, *opts.(*arvados.GetOptions))
+                       },
+               },
+               {
+                       arvados.EndpointContainerUnlock,
+                       func() interface{} {
+                               return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
+                       },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.ContainerUnlock(ctx, *opts.(*arvados.GetOptions))
+                       },
+               },
+               {
+                       arvados.EndpointContainerSSH,
+                       func() interface{} { return &arvados.ContainerSSHOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.ContainerSSH(ctx, *opts.(*arvados.ContainerSSHOptions))
+                       },
+               },
+               {
+                       arvados.EndpointContainerSSHCompat,
+                       func() interface{} { return &arvados.ContainerSSHOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.ContainerSSH(ctx, *opts.(*arvados.ContainerSSHOptions))
+                       },
+               },
+               {
+                       // arvados-client built before commit
+                       // bdc29d3129f6d75aa9ce0a24ffb849a272b06f08
+                       // used GET with params in headers instead of
+                       // POST form
+                       arvados.APIEndpoint{"GET", "arvados/v1/connect/{uuid}/ssh", ""},
+                       func() interface{} { return &arvados.ContainerSSHOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return nil, httpError(http.StatusGone, fmt.Errorf("API endpoint is obsolete -- please upgrade your arvados-client program"))
+                       },
+               },
+               {
+                       arvados.EndpointContainerGatewayTunnel,
+                       func() interface{} { return &arvados.ContainerGatewayTunnelOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.ContainerGatewayTunnel(ctx, *opts.(*arvados.ContainerGatewayTunnelOptions))
+                       },
+               },
+               {
+                       arvados.EndpointContainerGatewayTunnelCompat,
+                       func() interface{} { return &arvados.ContainerGatewayTunnelOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.ContainerGatewayTunnel(ctx, *opts.(*arvados.ContainerGatewayTunnelOptions))
+                       },
+               },
                {
                        arvados.EndpointContainerRequestCreate,
                        func() interface{} { return &arvados.CreateOptions{} },
@@ -220,28 +319,17 @@ func (rtr *router) addRoutes() {
                        },
                },
                {
-                       arvados.EndpointContainerLock,
-                       func() interface{} {
-                               return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
-                       },
-                       func(ctx context.Context, opts interface{}) (interface{}, error) {
-                               return rtr.backend.ContainerLock(ctx, *opts.(*arvados.GetOptions))
-                       },
-               },
-               {
-                       arvados.EndpointContainerUnlock,
-                       func() interface{} {
-                               return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
-                       },
+                       arvados.EndpointContainerRequestContainerStatus,
+                       func() interface{} { return &arvados.GetOptions{} },
                        func(ctx context.Context, opts interface{}) (interface{}, error) {
-                               return rtr.backend.ContainerUnlock(ctx, *opts.(*arvados.GetOptions))
+                               return rtr.backend.ContainerRequestContainerStatus(ctx, *opts.(*arvados.GetOptions))
                        },
                },
                {
-                       arvados.EndpointContainerSSH,
-                       func() interface{} { return &arvados.ContainerSSHOptions{} },
+                       arvados.EndpointContainerRequestLog,
+                       func() interface{} { return &arvados.ContainerLogOptions{} },
                        func(ctx context.Context, opts interface{}) (interface{}, error) {
-                               return rtr.backend.ContainerSSH(ctx, *opts.(*arvados.ContainerSSHOptions))
+                               return rtr.backend.ContainerRequestLog(ctx, *opts.(*arvados.ContainerLogOptions))
                        },
                },
                {
@@ -349,6 +437,41 @@ func (rtr *router) addRoutes() {
                                return rtr.backend.LinkDelete(ctx, *opts.(*arvados.DeleteOptions))
                        },
                },
+               {
+                       arvados.EndpointLogCreate,
+                       func() interface{} { return &arvados.CreateOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.LogCreate(ctx, *opts.(*arvados.CreateOptions))
+                       },
+               },
+               {
+                       arvados.EndpointLogUpdate,
+                       func() interface{} { return &arvados.UpdateOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.LogUpdate(ctx, *opts.(*arvados.UpdateOptions))
+                       },
+               },
+               {
+                       arvados.EndpointLogList,
+                       func() interface{} { return &arvados.ListOptions{Limit: -1} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.LogList(ctx, *opts.(*arvados.ListOptions))
+                       },
+               },
+               {
+                       arvados.EndpointLogGet,
+                       func() interface{} { return &arvados.GetOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.LogGet(ctx, *opts.(*arvados.GetOptions))
+                       },
+               },
+               {
+                       arvados.EndpointLogDelete,
+                       func() interface{} { return &arvados.DeleteOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.LogDelete(ctx, *opts.(*arvados.DeleteOptions))
+                       },
+               },
                {
                        arvados.EndpointSpecimenCreate,
                        func() interface{} { return &arvados.CreateOptions{} },
@@ -384,6 +507,48 @@ func (rtr *router) addRoutes() {
                                return rtr.backend.SpecimenDelete(ctx, *opts.(*arvados.DeleteOptions))
                        },
                },
+               {
+                       arvados.EndpointAPIClientAuthorizationCreate,
+                       func() interface{} { return &arvados.CreateOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.APIClientAuthorizationCreate(ctx, *opts.(*arvados.CreateOptions))
+                       },
+               },
+               {
+                       arvados.EndpointAPIClientAuthorizationUpdate,
+                       func() interface{} { return &arvados.UpdateOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.APIClientAuthorizationUpdate(ctx, *opts.(*arvados.UpdateOptions))
+                       },
+               },
+               {
+                       arvados.EndpointAPIClientAuthorizationDelete,
+                       func() interface{} { return &arvados.DeleteOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.APIClientAuthorizationDelete(ctx, *opts.(*arvados.DeleteOptions))
+                       },
+               },
+               {
+                       arvados.EndpointAPIClientAuthorizationList,
+                       func() interface{} { return &arvados.ListOptions{Limit: -1} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.APIClientAuthorizationList(ctx, *opts.(*arvados.ListOptions))
+                       },
+               },
+               {
+                       arvados.EndpointAPIClientAuthorizationCurrent,
+                       func() interface{} { return &arvados.GetOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.APIClientAuthorizationCurrent(ctx, *opts.(*arvados.GetOptions))
+                       },
+               },
+               {
+                       arvados.EndpointAPIClientAuthorizationGet,
+                       func() interface{} { return &arvados.GetOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.backend.APIClientAuthorizationGet(ctx, *opts.(*arvados.GetOptions))
+                       },
+               },
                {
                        arvados.EndpointUserCreate,
                        func() interface{} { return &arvados.CreateOptions{} },
@@ -483,9 +648,23 @@ func (rtr *router) addRoutes() {
                rtr.addRoute(route.endpoint, route.defaultOpts, exec)
        }
        rtr.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+               if req.Method == "OPTIONS" {
+                       // For non-webdav endpoints, return an empty
+                       // response with the CORS headers we already
+                       // added in ServeHTTP.
+                       w.WriteHeader(http.StatusOK)
+                       return
+               }
                httpserver.Errors(w, []string{"API endpoint not found"}, http.StatusNotFound)
        })
        rtr.mux.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+               if req.Method == "OPTIONS" {
+                       // For non-webdav endpoints, return an empty
+                       // response with the CORS headers we already
+                       // added in ServeHTTP.
+                       w.WriteHeader(http.StatusOK)
+                       return
+               }
                httpserver.Errors(w, []string{"API endpoint not found"}, http.StatusMethodNotAllowed)
        })
 }
@@ -500,9 +679,14 @@ func (rtr *router) addRoute(endpoint arvados.APIEndpoint, defaultOpts func() int
        if alt, ok := altMethod[endpoint.Method]; ok {
                methods = append(methods, alt)
        }
+       if strings.HasSuffix(endpoint.Path, ".*}") {
+               // webdav methods
+               methods = append(methods, "OPTIONS", "PROPFIND")
+       }
        rtr.mux.Methods(methods...).Path("/" + endpoint.Path).HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
                logger := ctxlog.FromContext(req.Context())
-               params, err := rtr.loadRequestParams(req, endpoint.AttrsKey)
+               opts := defaultOpts()
+               params, err := rtr.loadRequestParams(req, endpoint.AttrsKey, opts)
                if err != nil {
                        logger.WithFields(logrus.Fields{
                                "req":      req,
@@ -512,13 +696,6 @@ func (rtr *router) addRoute(endpoint arvados.APIEndpoint, defaultOpts func() int
                        rtr.sendError(w, err)
                        return
                }
-               opts := defaultOpts()
-               err = rtr.transcode(params, opts)
-               if err != nil {
-                       logger.WithField("params", params).WithError(err).Debugf("error transcoding params to %T", opts)
-                       rtr.sendError(w, err)
-                       return
-               }
                respOpts, err := rtr.responseOptions(opts)
                if err != nil {
                        logger.WithField("opts", opts).WithError(err).Debugf("error getting response options from %T", opts)
@@ -541,6 +718,26 @@ func (rtr *router) addRoute(endpoint arvados.APIEndpoint, defaultOpts func() int
                }
                ctx := auth.NewContext(req.Context(), creds)
                ctx = arvados.ContextWithRequestID(ctx, req.Header.Get("X-Request-Id"))
+               req = req.WithContext(ctx)
+
+               // Extract the token UUIDs (or a placeholder for v1 tokens)
+               var tokenUUIDs []string
+               for _, t := range creds.Tokens {
+                       if strings.HasPrefix(t, "v2/") {
+                               tokenParts := strings.Split(t, "/")
+                               if len(tokenParts) >= 3 {
+                                       tokenUUIDs = append(tokenUUIDs, tokenParts[1])
+                               }
+                       } else {
+                               end := t
+                               if len(t) > 5 {
+                                       end = t[len(t)-5:]
+                               }
+                               tokenUUIDs = append(tokenUUIDs, "v1 token ending in "+end)
+                       }
+               }
+               httpserver.SetResponseLogFields(ctx, logrus.Fields{"tokenUUIDs": tokenUUIDs})
+
                logger.WithFields(logrus.Fields{
                        "apiEndpoint": endpoint,
                        "apiOptsType": fmt.Sprintf("%T", opts),
@@ -561,13 +758,11 @@ func (rtr *router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        case "login", "logout", "auth":
        default:
                w.Header().Set("Access-Control-Allow-Origin", "*")
-               w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, PUT, POST, PATCH, DELETE")
-               w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Http-Method-Override")
+               w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS, PROPFIND, PUT, POST, PATCH, DELETE")
+               w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, Range, X-Http-Method-Override")
+               w.Header().Set("Access-Control-Expose-Headers", "Content-Range")
                w.Header().Set("Access-Control-Max-Age", "86486400")
        }
-       if r.Method == "OPTIONS" {
-               return
-       }
        if r.Body != nil {
                // Wrap r.Body in a http.MaxBytesReader(), otherwise
                // r.ParseForm() uses a default max request body size