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 "github.com/julienschmidt/httprouter"
18 "github.com/sirupsen/logrus"
22 mux *httprouter.Router
23 fed federation.Interface
26 func New(cluster *arvados.Cluster) *router {
28 mux: httprouter.New(),
29 fed: federation.New(cluster),
31 rtr.addRoutes(cluster)
35 func (rtr *router) addRoutes(cluster *arvados.Cluster) {
36 for _, route := range []struct {
37 endpoint arvados.APIEndpoint
38 defaultOpts func() interface{}
39 exec func(ctx context.Context, opts interface{}) (interface{}, error)
42 arvados.EndpointCollectionCreate,
43 func() interface{} { return &arvados.CreateOptions{} },
44 func(ctx context.Context, opts interface{}) (interface{}, error) {
45 return rtr.fed.CollectionCreate(ctx, *opts.(*arvados.CreateOptions))
49 arvados.EndpointCollectionUpdate,
50 func() interface{} { return &arvados.UpdateOptions{} },
51 func(ctx context.Context, opts interface{}) (interface{}, error) {
52 return rtr.fed.CollectionUpdate(ctx, *opts.(*arvados.UpdateOptions))
56 arvados.EndpointCollectionGet,
57 func() interface{} { return &arvados.GetOptions{} },
58 func(ctx context.Context, opts interface{}) (interface{}, error) {
59 return rtr.fed.CollectionGet(ctx, *opts.(*arvados.GetOptions))
63 arvados.EndpointCollectionList,
64 func() interface{} { return &arvados.ListOptions{Limit: -1} },
65 func(ctx context.Context, opts interface{}) (interface{}, error) {
66 return rtr.fed.CollectionList(ctx, *opts.(*arvados.ListOptions))
70 arvados.EndpointCollectionProvenance,
71 func() interface{} { return &arvados.GetOptions{} },
72 func(ctx context.Context, opts interface{}) (interface{}, error) {
73 return rtr.fed.CollectionProvenance(ctx, *opts.(*arvados.GetOptions))
77 arvados.EndpointCollectionUsedBy,
78 func() interface{} { return &arvados.GetOptions{} },
79 func(ctx context.Context, opts interface{}) (interface{}, error) {
80 return rtr.fed.CollectionUsedBy(ctx, *opts.(*arvados.GetOptions))
84 arvados.EndpointCollectionDelete,
85 func() interface{} { return &arvados.DeleteOptions{} },
86 func(ctx context.Context, opts interface{}) (interface{}, error) {
87 return rtr.fed.CollectionDelete(ctx, *opts.(*arvados.DeleteOptions))
91 arvados.EndpointContainerCreate,
92 func() interface{} { return &arvados.CreateOptions{} },
93 func(ctx context.Context, opts interface{}) (interface{}, error) {
94 return rtr.fed.ContainerCreate(ctx, *opts.(*arvados.CreateOptions))
98 arvados.EndpointContainerUpdate,
99 func() interface{} { return &arvados.UpdateOptions{} },
100 func(ctx context.Context, opts interface{}) (interface{}, error) {
101 return rtr.fed.ContainerUpdate(ctx, *opts.(*arvados.UpdateOptions))
105 arvados.EndpointContainerGet,
106 func() interface{} { return &arvados.GetOptions{} },
107 func(ctx context.Context, opts interface{}) (interface{}, error) {
108 return rtr.fed.ContainerGet(ctx, *opts.(*arvados.GetOptions))
112 arvados.EndpointContainerList,
113 func() interface{} { return &arvados.ListOptions{Limit: -1} },
114 func(ctx context.Context, opts interface{}) (interface{}, error) {
115 return rtr.fed.ContainerList(ctx, *opts.(*arvados.ListOptions))
119 arvados.EndpointContainerDelete,
120 func() interface{} { return &arvados.DeleteOptions{} },
121 func(ctx context.Context, opts interface{}) (interface{}, error) {
122 return rtr.fed.ContainerDelete(ctx, *opts.(*arvados.DeleteOptions))
126 arvados.EndpointContainerLock,
128 return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
130 func(ctx context.Context, opts interface{}) (interface{}, error) {
131 return rtr.fed.ContainerLock(ctx, *opts.(*arvados.GetOptions))
135 arvados.EndpointContainerUnlock,
137 return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
139 func(ctx context.Context, opts interface{}) (interface{}, error) {
140 return rtr.fed.ContainerUnlock(ctx, *opts.(*arvados.GetOptions))
144 arvados.EndpointSpecimenCreate,
145 func() interface{} { return &arvados.CreateOptions{} },
146 func(ctx context.Context, opts interface{}) (interface{}, error) {
147 return rtr.fed.SpecimenCreate(ctx, *opts.(*arvados.CreateOptions))
151 arvados.EndpointSpecimenUpdate,
152 func() interface{} { return &arvados.UpdateOptions{} },
153 func(ctx context.Context, opts interface{}) (interface{}, error) {
154 return rtr.fed.SpecimenUpdate(ctx, *opts.(*arvados.UpdateOptions))
158 arvados.EndpointSpecimenGet,
159 func() interface{} { return &arvados.GetOptions{} },
160 func(ctx context.Context, opts interface{}) (interface{}, error) {
161 return rtr.fed.SpecimenGet(ctx, *opts.(*arvados.GetOptions))
165 arvados.EndpointSpecimenList,
166 func() interface{} { return &arvados.ListOptions{Limit: -1} },
167 func(ctx context.Context, opts interface{}) (interface{}, error) {
168 return rtr.fed.SpecimenList(ctx, *opts.(*arvados.ListOptions))
172 arvados.EndpointSpecimenDelete,
173 func() interface{} { return &arvados.DeleteOptions{} },
174 func(ctx context.Context, opts interface{}) (interface{}, error) {
175 return rtr.fed.SpecimenDelete(ctx, *opts.(*arvados.DeleteOptions))
180 methods := []string{route.endpoint.Method}
181 if route.endpoint.Method == "PATCH" {
182 methods = append(methods, "PUT")
184 for _, method := range methods {
185 rtr.mux.HandlerFunc(method, "/"+route.endpoint.Path, func(w http.ResponseWriter, req *http.Request) {
186 logger := ctxlog.FromContext(req.Context())
187 params, err := rtr.loadRequestParams(req, route.endpoint.AttrsKey)
189 logger.WithField("req", req).WithField("route", route).WithError(err).Debug("error loading request params")
190 rtr.sendError(w, err)
193 opts := route.defaultOpts()
194 err = rtr.transcode(params, opts)
196 logger.WithField("params", params).WithError(err).Debugf("error transcoding params to %T", opts)
197 rtr.sendError(w, err)
200 respOpts, err := rtr.responseOptions(opts)
202 logger.WithField("opts", opts).WithError(err).Debugf("error getting response options from %T", opts)
203 rtr.sendError(w, err)
207 creds := auth.CredentialsFromRequest(req)
208 if rt, _ := params["reader_tokens"].([]interface{}); len(rt) > 0 {
209 for _, t := range rt {
210 if t, ok := t.(string); ok {
211 creds.Tokens = append(creds.Tokens, t)
216 ctx = context.WithValue(ctx, auth.ContextKeyCredentials, creds)
217 ctx = arvados.ContextWithRequestID(ctx, req.Header.Get("X-Request-Id"))
218 logger.WithFields(logrus.Fields{
219 "apiEndpoint": route.endpoint,
220 "apiOptsType": fmt.Sprintf("%T", opts),
223 resp, err := route.exec(ctx, opts)
225 logger.WithError(err).Debugf("returning error type %T", err)
226 rtr.sendError(w, err)
229 rtr.sendResponse(w, resp, respOpts)
235 func (rtr *router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
236 switch strings.SplitN(strings.TrimLeft(r.URL.Path, "/"), "/", 2)[0] {
237 case "login", "logout", "auth":
239 w.Header().Set("Access-Control-Allow-Origin", "*")
240 w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, PUT, POST, DELETE")
241 w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
242 w.Header().Set("Access-Control-Max-Age", "86486400")
244 if r.Method == "OPTIONS" {
248 if m := r.FormValue("_method"); m != "" {
253 rtr.mux.ServeHTTP(w, r)