1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
13 "git.curoverse.com/arvados.git/lib/controller/federation"
14 "git.curoverse.com/arvados.git/sdk/go/arvados"
15 "git.curoverse.com/arvados.git/sdk/go/auth"
16 "git.curoverse.com/arvados.git/sdk/go/ctxlog"
17 "git.curoverse.com/arvados.git/sdk/go/httpserver"
18 "github.com/julienschmidt/httprouter"
19 "github.com/sirupsen/logrus"
23 mux *httprouter.Router
24 fed federation.Interface
27 func New(cluster *arvados.Cluster) *router {
29 mux: httprouter.New(),
30 fed: federation.New(cluster),
32 rtr.addRoutes(cluster)
36 func (rtr *router) addRoutes(cluster *arvados.Cluster) {
37 for _, route := range []struct {
38 endpoint arvados.APIEndpoint
39 defaultOpts func() interface{}
40 exec func(ctx context.Context, opts interface{}) (interface{}, error)
43 arvados.EndpointCollectionCreate,
44 func() interface{} { return &arvados.CreateOptions{} },
45 func(ctx context.Context, opts interface{}) (interface{}, error) {
46 return rtr.fed.CollectionCreate(ctx, *opts.(*arvados.CreateOptions))
50 arvados.EndpointCollectionUpdate,
51 func() interface{} { return &arvados.UpdateOptions{} },
52 func(ctx context.Context, opts interface{}) (interface{}, error) {
53 return rtr.fed.CollectionUpdate(ctx, *opts.(*arvados.UpdateOptions))
57 arvados.EndpointCollectionGet,
58 func() interface{} { return &arvados.GetOptions{} },
59 func(ctx context.Context, opts interface{}) (interface{}, error) {
60 return rtr.fed.CollectionGet(ctx, *opts.(*arvados.GetOptions))
64 arvados.EndpointCollectionList,
65 func() interface{} { return &arvados.ListOptions{Limit: -1} },
66 func(ctx context.Context, opts interface{}) (interface{}, error) {
67 return rtr.fed.CollectionList(ctx, *opts.(*arvados.ListOptions))
71 arvados.EndpointCollectionProvenance,
72 func() interface{} { return &arvados.GetOptions{} },
73 func(ctx context.Context, opts interface{}) (interface{}, error) {
74 return rtr.fed.CollectionProvenance(ctx, *opts.(*arvados.GetOptions))
78 arvados.EndpointCollectionUsedBy,
79 func() interface{} { return &arvados.GetOptions{} },
80 func(ctx context.Context, opts interface{}) (interface{}, error) {
81 return rtr.fed.CollectionUsedBy(ctx, *opts.(*arvados.GetOptions))
85 arvados.EndpointCollectionDelete,
86 func() interface{} { return &arvados.DeleteOptions{} },
87 func(ctx context.Context, opts interface{}) (interface{}, error) {
88 return rtr.fed.CollectionDelete(ctx, *opts.(*arvados.DeleteOptions))
92 arvados.EndpointCollectionTrash,
93 func() interface{} { return &arvados.DeleteOptions{} },
94 func(ctx context.Context, opts interface{}) (interface{}, error) {
95 return rtr.fed.CollectionTrash(ctx, *opts.(*arvados.DeleteOptions))
99 arvados.EndpointCollectionUntrash,
100 func() interface{} { return &arvados.UntrashOptions{} },
101 func(ctx context.Context, opts interface{}) (interface{}, error) {
102 return rtr.fed.CollectionUntrash(ctx, *opts.(*arvados.UntrashOptions))
106 arvados.EndpointContainerCreate,
107 func() interface{} { return &arvados.CreateOptions{} },
108 func(ctx context.Context, opts interface{}) (interface{}, error) {
109 return rtr.fed.ContainerCreate(ctx, *opts.(*arvados.CreateOptions))
113 arvados.EndpointContainerUpdate,
114 func() interface{} { return &arvados.UpdateOptions{} },
115 func(ctx context.Context, opts interface{}) (interface{}, error) {
116 return rtr.fed.ContainerUpdate(ctx, *opts.(*arvados.UpdateOptions))
120 arvados.EndpointContainerGet,
121 func() interface{} { return &arvados.GetOptions{} },
122 func(ctx context.Context, opts interface{}) (interface{}, error) {
123 return rtr.fed.ContainerGet(ctx, *opts.(*arvados.GetOptions))
127 arvados.EndpointContainerList,
128 func() interface{} { return &arvados.ListOptions{Limit: -1} },
129 func(ctx context.Context, opts interface{}) (interface{}, error) {
130 return rtr.fed.ContainerList(ctx, *opts.(*arvados.ListOptions))
134 arvados.EndpointContainerDelete,
135 func() interface{} { return &arvados.DeleteOptions{} },
136 func(ctx context.Context, opts interface{}) (interface{}, error) {
137 return rtr.fed.ContainerDelete(ctx, *opts.(*arvados.DeleteOptions))
141 arvados.EndpointContainerLock,
143 return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
145 func(ctx context.Context, opts interface{}) (interface{}, error) {
146 return rtr.fed.ContainerLock(ctx, *opts.(*arvados.GetOptions))
150 arvados.EndpointContainerUnlock,
152 return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
154 func(ctx context.Context, opts interface{}) (interface{}, error) {
155 return rtr.fed.ContainerUnlock(ctx, *opts.(*arvados.GetOptions))
159 arvados.EndpointSpecimenCreate,
160 func() interface{} { return &arvados.CreateOptions{} },
161 func(ctx context.Context, opts interface{}) (interface{}, error) {
162 return rtr.fed.SpecimenCreate(ctx, *opts.(*arvados.CreateOptions))
166 arvados.EndpointSpecimenUpdate,
167 func() interface{} { return &arvados.UpdateOptions{} },
168 func(ctx context.Context, opts interface{}) (interface{}, error) {
169 return rtr.fed.SpecimenUpdate(ctx, *opts.(*arvados.UpdateOptions))
173 arvados.EndpointSpecimenGet,
174 func() interface{} { return &arvados.GetOptions{} },
175 func(ctx context.Context, opts interface{}) (interface{}, error) {
176 return rtr.fed.SpecimenGet(ctx, *opts.(*arvados.GetOptions))
180 arvados.EndpointSpecimenList,
181 func() interface{} { return &arvados.ListOptions{Limit: -1} },
182 func(ctx context.Context, opts interface{}) (interface{}, error) {
183 return rtr.fed.SpecimenList(ctx, *opts.(*arvados.ListOptions))
187 arvados.EndpointSpecimenDelete,
188 func() interface{} { return &arvados.DeleteOptions{} },
189 func(ctx context.Context, opts interface{}) (interface{}, error) {
190 return rtr.fed.SpecimenDelete(ctx, *opts.(*arvados.DeleteOptions))
195 methods := []string{route.endpoint.Method}
196 if route.endpoint.Method == "PATCH" {
197 methods = append(methods, "PUT")
199 for _, method := range methods {
200 rtr.mux.HandlerFunc(method, "/"+route.endpoint.Path, func(w http.ResponseWriter, req *http.Request) {
201 logger := ctxlog.FromContext(req.Context())
202 params, err := rtr.loadRequestParams(req, route.endpoint.AttrsKey)
204 logger.WithField("req", req).WithField("route", route).WithError(err).Debug("error loading request params")
205 rtr.sendError(w, err)
208 opts := route.defaultOpts()
209 err = rtr.transcode(params, opts)
211 logger.WithField("params", params).WithError(err).Debugf("error transcoding params to %T", opts)
212 rtr.sendError(w, err)
215 respOpts, err := rtr.responseOptions(opts)
217 logger.WithField("opts", opts).WithError(err).Debugf("error getting response options from %T", opts)
218 rtr.sendError(w, err)
222 creds := auth.CredentialsFromRequest(req)
223 if rt, _ := params["reader_tokens"].([]interface{}); len(rt) > 0 {
224 for _, t := range rt {
225 if t, ok := t.(string); ok {
226 creds.Tokens = append(creds.Tokens, t)
231 ctx = context.WithValue(ctx, auth.ContextKeyCredentials, creds)
232 ctx = arvados.ContextWithRequestID(ctx, req.Header.Get("X-Request-Id"))
233 logger.WithFields(logrus.Fields{
234 "apiEndpoint": route.endpoint,
235 "apiOptsType": fmt.Sprintf("%T", opts),
238 resp, err := route.exec(ctx, opts)
240 logger.WithError(err).Debugf("returning error type %T", err)
241 rtr.sendError(w, err)
244 rtr.sendResponse(w, resp, respOpts)
248 rtr.mux.NotFound = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
249 httpserver.Errors(w, []string{"API endpoint not found"}, http.StatusNotFound)
253 func (rtr *router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
254 switch strings.SplitN(strings.TrimLeft(r.URL.Path, "/"), "/", 2)[0] {
255 case "login", "logout", "auth":
257 w.Header().Set("Access-Control-Allow-Origin", "*")
258 w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, PUT, POST, DELETE")
259 w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
260 w.Header().Set("Access-Control-Max-Age", "86486400")
262 if r.Method == "OPTIONS" {
266 if m := r.FormValue("_method"); m != "" {
271 rtr.mux.ServeHTTP(w, r)