14287: Handle collection/.../provenance and .../used_by requests.
[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         "net/http"
10
11         "git.curoverse.com/arvados.git/lib/controller/federation"
12         "git.curoverse.com/arvados.git/sdk/go/arvados"
13         "git.curoverse.com/arvados.git/sdk/go/auth"
14         "git.curoverse.com/arvados.git/sdk/go/ctxlog"
15         "github.com/julienschmidt/httprouter"
16 )
17
18 type router struct {
19         mux *httprouter.Router
20         fed federation.Interface
21 }
22
23 func New(cluster *arvados.Cluster) *router {
24         rtr := &router{
25                 mux: httprouter.New(),
26                 fed: federation.New(cluster),
27         }
28         rtr.addRoutes(cluster)
29         return rtr
30 }
31
32 func (rtr *router) addRoutes(cluster *arvados.Cluster) {
33         for _, route := range []struct {
34                 endpoint    arvados.APIEndpoint
35                 defaultOpts func() interface{}
36                 exec        func(ctx context.Context, opts interface{}) (interface{}, error)
37         }{
38                 {
39                         arvados.EndpointCollectionCreate,
40                         func() interface{} { return &arvados.CreateOptions{} },
41                         func(ctx context.Context, opts interface{}) (interface{}, error) {
42                                 return rtr.fed.CollectionCreate(ctx, *opts.(*arvados.CreateOptions))
43                         },
44                 },
45                 {
46                         arvados.EndpointCollectionUpdate,
47                         func() interface{} { return &arvados.UpdateOptions{} },
48                         func(ctx context.Context, opts interface{}) (interface{}, error) {
49                                 return rtr.fed.CollectionUpdate(ctx, *opts.(*arvados.UpdateOptions))
50                         },
51                 },
52                 {
53                         arvados.EndpointCollectionGet,
54                         func() interface{} { return &arvados.GetOptions{} },
55                         func(ctx context.Context, opts interface{}) (interface{}, error) {
56                                 return rtr.fed.CollectionGet(ctx, *opts.(*arvados.GetOptions))
57                         },
58                 },
59                 {
60                         arvados.EndpointCollectionList,
61                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
62                         func(ctx context.Context, opts interface{}) (interface{}, error) {
63                                 return rtr.fed.CollectionList(ctx, *opts.(*arvados.ListOptions))
64                         },
65                 },
66                 {
67                         arvados.EndpointCollectionProvenance,
68                         func() interface{} { return &arvados.GetOptions{} },
69                         func(ctx context.Context, opts interface{}) (interface{}, error) {
70                                 return rtr.fed.CollectionProvenance(ctx, *opts.(*arvados.GetOptions))
71                         },
72                 },
73                 {
74                         arvados.EndpointCollectionUsedBy,
75                         func() interface{} { return &arvados.GetOptions{} },
76                         func(ctx context.Context, opts interface{}) (interface{}, error) {
77                                 return rtr.fed.CollectionUsedBy(ctx, *opts.(*arvados.GetOptions))
78                         },
79                 },
80                 {
81                         arvados.EndpointCollectionDelete,
82                         func() interface{} { return &arvados.DeleteOptions{} },
83                         func(ctx context.Context, opts interface{}) (interface{}, error) {
84                                 return rtr.fed.CollectionDelete(ctx, *opts.(*arvados.DeleteOptions))
85                         },
86                 },
87                 {
88                         arvados.EndpointContainerCreate,
89                         func() interface{} { return &arvados.CreateOptions{} },
90                         func(ctx context.Context, opts interface{}) (interface{}, error) {
91                                 return rtr.fed.ContainerCreate(ctx, *opts.(*arvados.CreateOptions))
92                         },
93                 },
94                 {
95                         arvados.EndpointContainerUpdate,
96                         func() interface{} { return &arvados.UpdateOptions{} },
97                         func(ctx context.Context, opts interface{}) (interface{}, error) {
98                                 return rtr.fed.ContainerUpdate(ctx, *opts.(*arvados.UpdateOptions))
99                         },
100                 },
101                 {
102                         arvados.EndpointContainerGet,
103                         func() interface{} { return &arvados.GetOptions{} },
104                         func(ctx context.Context, opts interface{}) (interface{}, error) {
105                                 return rtr.fed.ContainerGet(ctx, *opts.(*arvados.GetOptions))
106                         },
107                 },
108                 {
109                         arvados.EndpointContainerList,
110                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
111                         func(ctx context.Context, opts interface{}) (interface{}, error) {
112                                 return rtr.fed.ContainerList(ctx, *opts.(*arvados.ListOptions))
113                         },
114                 },
115                 {
116                         arvados.EndpointContainerDelete,
117                         func() interface{} { return &arvados.DeleteOptions{} },
118                         func(ctx context.Context, opts interface{}) (interface{}, error) {
119                                 return rtr.fed.ContainerDelete(ctx, *opts.(*arvados.DeleteOptions))
120                         },
121                 },
122                 {
123                         arvados.EndpointContainerLock,
124                         func() interface{} {
125                                 return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
126                         },
127                         func(ctx context.Context, opts interface{}) (interface{}, error) {
128                                 return rtr.fed.ContainerLock(ctx, *opts.(*arvados.GetOptions))
129                         },
130                 },
131                 {
132                         arvados.EndpointContainerUnlock,
133                         func() interface{} {
134                                 return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
135                         },
136                         func(ctx context.Context, opts interface{}) (interface{}, error) {
137                                 return rtr.fed.ContainerUnlock(ctx, *opts.(*arvados.GetOptions))
138                         },
139                 },
140                 {
141                         arvados.EndpointSpecimenCreate,
142                         func() interface{} { return &arvados.CreateOptions{} },
143                         func(ctx context.Context, opts interface{}) (interface{}, error) {
144                                 return rtr.fed.SpecimenCreate(ctx, *opts.(*arvados.CreateOptions))
145                         },
146                 },
147                 {
148                         arvados.EndpointSpecimenUpdate,
149                         func() interface{} { return &arvados.UpdateOptions{} },
150                         func(ctx context.Context, opts interface{}) (interface{}, error) {
151                                 return rtr.fed.SpecimenUpdate(ctx, *opts.(*arvados.UpdateOptions))
152                         },
153                 },
154                 {
155                         arvados.EndpointSpecimenGet,
156                         func() interface{} { return &arvados.GetOptions{} },
157                         func(ctx context.Context, opts interface{}) (interface{}, error) {
158                                 return rtr.fed.SpecimenGet(ctx, *opts.(*arvados.GetOptions))
159                         },
160                 },
161                 {
162                         arvados.EndpointSpecimenList,
163                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
164                         func(ctx context.Context, opts interface{}) (interface{}, error) {
165                                 return rtr.fed.SpecimenList(ctx, *opts.(*arvados.ListOptions))
166                         },
167                 },
168                 {
169                         arvados.EndpointSpecimenDelete,
170                         func() interface{} { return &arvados.DeleteOptions{} },
171                         func(ctx context.Context, opts interface{}) (interface{}, error) {
172                                 return rtr.fed.SpecimenDelete(ctx, *opts.(*arvados.DeleteOptions))
173                         },
174                 },
175         } {
176                 route := route
177                 methods := []string{route.endpoint.Method}
178                 if route.endpoint.Method == "PATCH" {
179                         methods = append(methods, "PUT")
180                 }
181                 for _, method := range methods {
182                         rtr.mux.HandlerFunc(method, "/"+route.endpoint.Path, func(w http.ResponseWriter, req *http.Request) {
183                                 params, err := rtr.loadRequestParams(req, route.endpoint.AttrsKey)
184                                 if err != nil {
185                                         rtr.sendError(w, err)
186                                         return
187                                 }
188                                 opts := route.defaultOpts()
189                                 err = rtr.transcode(params, opts)
190                                 if err != nil {
191                                         rtr.sendError(w, err)
192                                         return
193                                 }
194                                 respOpts, err := rtr.responseOptions(opts)
195                                 if err != nil {
196                                         rtr.sendError(w, err)
197                                         return
198                                 }
199
200                                 creds := auth.CredentialsFromRequest(req)
201                                 ctx := req.Context()
202                                 ctx = context.WithValue(ctx, auth.ContextKeyCredentials, creds)
203                                 ctx = arvados.ContextWithRequestID(ctx, req.Header.Get("X-Request-Id"))
204                                 resp, err := route.exec(ctx, opts)
205                                 if err != nil {
206                                         ctxlog.FromContext(ctx).WithError(err).Debugf("returning error response for %#v", err)
207                                         rtr.sendError(w, err)
208                                         return
209                                 }
210                                 rtr.sendResponse(w, resp, respOpts)
211                         })
212                 }
213         }
214 }
215
216 func (rtr *router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
217         r.ParseForm()
218         if m := r.FormValue("_method"); m != "" {
219                 r2 := *r
220                 r = &r2
221                 r.Method = m
222         }
223         rtr.mux.ServeHTTP(w, r)
224 }