14287: Clean up context key usage.
[arvados.git] / lib / controller / router / router.go
index 38c2e374ea7c445316e2fedc968d72f920ff9e79..f37c7ea9073ac51c0553ecf03c91ff4a9b1b2e92 100644 (file)
@@ -6,6 +6,7 @@ package router
 
 import (
        "context"
+       "fmt"
        "net/http"
        "strings"
 
@@ -13,12 +14,14 @@ import (
        "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/auth"
        "git.curoverse.com/arvados.git/sdk/go/ctxlog"
+       "git.curoverse.com/arvados.git/sdk/go/httpserver"
        "github.com/julienschmidt/httprouter"
+       "github.com/sirupsen/logrus"
 )
 
 type router struct {
        mux *httprouter.Router
-       fed federation.Interface
+       fed arvados.API
 }
 
 func New(cluster *arvados.Cluster) *router {
@@ -26,15 +29,17 @@ func New(cluster *arvados.Cluster) *router {
                mux: httprouter.New(),
                fed: federation.New(cluster),
        }
-       rtr.addRoutes(cluster)
+       rtr.addRoutes()
        return rtr
 }
 
-func (rtr *router) addRoutes(cluster *arvados.Cluster) {
+type routableFunc func(ctx context.Context, opts interface{}) (interface{}, error)
+
+func (rtr *router) addRoutes() {
        for _, route := range []struct {
                endpoint    arvados.APIEndpoint
                defaultOpts func() interface{}
-               exec        func(ctx context.Context, opts interface{}) (interface{}, error)
+               exec        routableFunc
        }{
                {
                        arvados.EndpointCollectionCreate,
@@ -85,6 +90,20 @@ func (rtr *router) addRoutes(cluster *arvados.Cluster) {
                                return rtr.fed.CollectionDelete(ctx, *opts.(*arvados.DeleteOptions))
                        },
                },
+               {
+                       arvados.EndpointCollectionTrash,
+                       func() interface{} { return &arvados.DeleteOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.fed.CollectionTrash(ctx, *opts.(*arvados.DeleteOptions))
+                       },
+               },
+               {
+                       arvados.EndpointCollectionUntrash,
+                       func() interface{} { return &arvados.UntrashOptions{} },
+                       func(ctx context.Context, opts interface{}) (interface{}, error) {
+                               return rtr.fed.CollectionUntrash(ctx, *opts.(*arvados.UntrashOptions))
+                       },
+               },
                {
                        arvados.EndpointContainerCreate,
                        func() interface{} { return &arvados.CreateOptions{} },
@@ -174,51 +193,72 @@ func (rtr *router) addRoutes(cluster *arvados.Cluster) {
                        },
                },
        } {
-               route := route
-               methods := []string{route.endpoint.Method}
+               rtr.addRoute(route.endpoint, route.defaultOpts, route.exec)
                if route.endpoint.Method == "PATCH" {
-                       methods = append(methods, "PUT")
+                       // Accept PUT as a synonym for PATCH.
+                       endpointPUT := route.endpoint
+                       endpointPUT.Method = "PUT"
+                       rtr.addRoute(endpointPUT, route.defaultOpts, route.exec)
                }
-               for _, method := range methods {
-                       rtr.mux.HandlerFunc(method, "/"+route.endpoint.Path, func(w http.ResponseWriter, req *http.Request) {
-                               params, err := rtr.loadRequestParams(req, route.endpoint.AttrsKey)
-                               if err != nil {
-                                       rtr.sendError(w, err)
-                                       return
-                               }
-                               opts := route.defaultOpts()
-                               err = rtr.transcode(params, opts)
-                               if err != nil {
-                                       rtr.sendError(w, err)
-                                       return
-                               }
-                               respOpts, err := rtr.responseOptions(opts)
-                               if err != nil {
-                                       rtr.sendError(w, err)
-                                       return
-                               }
+       }
+       rtr.mux.NotFound = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+               httpserver.Errors(w, []string{"API endpoint not found"}, http.StatusNotFound)
+       })
+       rtr.mux.MethodNotAllowed = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+               httpserver.Errors(w, []string{"API endpoint not found"}, http.StatusMethodNotAllowed)
+       })
+}
 
-                               creds := auth.CredentialsFromRequest(req)
-                               if rt, _ := params["reader_tokens"].([]interface{}); len(rt) > 0 {
-                                       for _, t := range rt {
-                                               if t, ok := t.(string); ok {
-                                                       creds.Tokens = append(creds.Tokens, t)
-                                               }
-                                       }
-                               }
-                               ctx := req.Context()
-                               ctx = context.WithValue(ctx, auth.ContextKeyCredentials, creds)
-                               ctx = arvados.ContextWithRequestID(ctx, req.Header.Get("X-Request-Id"))
-                               resp, err := route.exec(ctx, opts)
-                               if err != nil {
-                                       ctxlog.FromContext(ctx).WithError(err).Debugf("returning error response for %#v", err)
-                                       rtr.sendError(w, err)
-                                       return
+func (rtr *router) addRoute(endpoint arvados.APIEndpoint, defaultOpts func() interface{}, exec routableFunc) {
+       rtr.mux.HandlerFunc(endpoint.Method, "/"+endpoint.Path, func(w http.ResponseWriter, req *http.Request) {
+               logger := ctxlog.FromContext(req.Context())
+               params, err := rtr.loadRequestParams(req, endpoint.AttrsKey)
+               if err != nil {
+                       logger.WithFields(logrus.Fields{
+                               "req":      req,
+                               "method":   endpoint.Method,
+                               "endpoint": endpoint,
+                       }).WithError(err).Debug("error loading request params")
+                       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)
+                       rtr.sendError(w, err)
+                       return
+               }
+
+               creds := auth.CredentialsFromRequest(req)
+               if rt, _ := params["reader_tokens"].([]interface{}); len(rt) > 0 {
+                       for _, t := range rt {
+                               if t, ok := t.(string); ok {
+                                       creds.Tokens = append(creds.Tokens, t)
                                }
-                               rtr.sendResponse(w, resp, respOpts)
-                       })
+                       }
                }
-       }
+               ctx := auth.NewContext(req.Context(), creds)
+               ctx = arvados.ContextWithRequestID(ctx, req.Header.Get("X-Request-Id"))
+               logger.WithFields(logrus.Fields{
+                       "apiEndpoint": endpoint,
+                       "apiOptsType": fmt.Sprintf("%T", opts),
+                       "apiOpts":     opts,
+               }).Debug("exec")
+               resp, err := exec(ctx, opts)
+               if err != nil {
+                       logger.WithError(err).Debugf("returning error type %T", err)
+                       rtr.sendError(w, err)
+                       return
+               }
+               rtr.sendResponse(w, resp, respOpts)
+       })
 }
 
 func (rtr *router) ServeHTTP(w http.ResponseWriter, r *http.Request) {