14813: Add CORS headers to "get config" by moving it to arvados.API.
[arvados.git] / lib / controller / rpc / conn.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package rpc
6
7 import (
8         "context"
9         "crypto/tls"
10         "encoding/json"
11         "errors"
12         "fmt"
13         "io"
14         "net"
15         "net/http"
16         "net/url"
17         "strings"
18         "time"
19
20         "git.curoverse.com/arvados.git/sdk/go/arvados"
21         "git.curoverse.com/arvados.git/sdk/go/auth"
22 )
23
24 type TokenProvider func(context.Context) ([]string, error)
25
26 func PassthroughTokenProvider(ctx context.Context) ([]string, error) {
27         if incoming, ok := auth.FromContext(ctx); !ok {
28                 return nil, errors.New("no token provided")
29         } else {
30                 return incoming.Tokens, nil
31         }
32 }
33
34 type Conn struct {
35         clusterID     string
36         httpClient    http.Client
37         baseURL       url.URL
38         tokenProvider TokenProvider
39 }
40
41 func NewConn(clusterID string, url *url.URL, insecure bool, tp TokenProvider) *Conn {
42         transport := http.DefaultTransport
43         if insecure {
44                 // It's not safe to copy *http.DefaultTransport
45                 // because it has a mutex (which might be locked)
46                 // protecting a private map (which might not be nil).
47                 // So we build our own, using the Go 1.12 default
48                 // values, ignoring any changes the application has
49                 // made to http.DefaultTransport.
50                 transport = &http.Transport{
51                         DialContext: (&net.Dialer{
52                                 Timeout:   30 * time.Second,
53                                 KeepAlive: 30 * time.Second,
54                                 DualStack: true,
55                         }).DialContext,
56                         MaxIdleConns:          100,
57                         IdleConnTimeout:       90 * time.Second,
58                         TLSHandshakeTimeout:   10 * time.Second,
59                         ExpectContinueTimeout: 1 * time.Second,
60                         TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
61                 }
62         }
63         return &Conn{
64                 clusterID:     clusterID,
65                 httpClient:    http.Client{Transport: transport},
66                 baseURL:       *url,
67                 tokenProvider: tp,
68         }
69 }
70
71 func (conn *Conn) requestAndDecode(ctx context.Context, dst interface{}, ep arvados.APIEndpoint, body io.Reader, opts interface{}) error {
72         aClient := arvados.Client{
73                 Client:  &conn.httpClient,
74                 Scheme:  conn.baseURL.Scheme,
75                 APIHost: conn.baseURL.Host,
76         }
77         tokens, err := conn.tokenProvider(ctx)
78         if err != nil {
79                 return err
80         } else if len(tokens) > 0 {
81                 ctx = arvados.ContextWithAuthorization(ctx, "Bearer "+tokens[0])
82         } else {
83                 // Use a non-empty auth string to ensure we override
84                 // any default token set on aClient -- and to avoid
85                 // having the remote prompt us to send a token by
86                 // responding 401.
87                 ctx = arvados.ContextWithAuthorization(ctx, "Bearer -")
88         }
89
90         // Encode opts to JSON and decode from there to a
91         // map[string]interface{}, so we can munge the query params
92         // using the JSON key names specified by opts' struct tags.
93         j, err := json.Marshal(opts)
94         if err != nil {
95                 return fmt.Errorf("%T: requestAndDecode: Marshal opts: %s", conn, err)
96         }
97         var params map[string]interface{}
98         err = json.Unmarshal(j, &params)
99         if err != nil {
100                 return fmt.Errorf("%T: requestAndDecode: Unmarshal opts: %s", conn, err)
101         }
102         if attrs, ok := params["attrs"]; ok && ep.AttrsKey != "" {
103                 params[ep.AttrsKey] = attrs
104                 delete(params, "attrs")
105         }
106         if limit, ok := params["limit"].(float64); ok && limit < 0 {
107                 // Negative limit means "not specified" here, but some
108                 // servers/versions do not accept that, so we need to
109                 // remove it entirely.
110                 delete(params, "limit")
111         }
112         if len(tokens) > 1 {
113                 params["reader_tokens"] = tokens[1:]
114         }
115         path := ep.Path
116         if strings.Contains(ep.Path, "/:uuid") {
117                 uuid, _ := params["uuid"].(string)
118                 path = strings.Replace(path, "/:uuid", "/"+uuid, 1)
119                 delete(params, "uuid")
120         }
121         return aClient.RequestAndDecodeContext(ctx, dst, ep.Method, path, body, params)
122 }
123
124 func (conn *Conn) ConfigGet(ctx context.Context) (json.RawMessage, error) {
125         ep := arvados.EndpointConfigGet
126         var resp json.RawMessage
127         err := conn.requestAndDecode(ctx, &resp, ep, nil, nil)
128         return resp, err
129 }
130
131 func (conn *Conn) CollectionCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Collection, error) {
132         ep := arvados.EndpointCollectionCreate
133         var resp arvados.Collection
134         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
135         return resp, err
136 }
137
138 func (conn *Conn) CollectionUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Collection, error) {
139         ep := arvados.EndpointCollectionUpdate
140         var resp arvados.Collection
141         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
142         return resp, err
143 }
144
145 func (conn *Conn) CollectionGet(ctx context.Context, options arvados.GetOptions) (arvados.Collection, error) {
146         ep := arvados.EndpointCollectionGet
147         var resp arvados.Collection
148         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
149         return resp, err
150 }
151
152 func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
153         ep := arvados.EndpointCollectionList
154         var resp arvados.CollectionList
155         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
156         return resp, err
157 }
158
159 func (conn *Conn) CollectionProvenance(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
160         ep := arvados.EndpointCollectionProvenance
161         var resp map[string]interface{}
162         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
163         return resp, err
164 }
165
166 func (conn *Conn) CollectionUsedBy(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
167         ep := arvados.EndpointCollectionUsedBy
168         var resp map[string]interface{}
169         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
170         return resp, err
171 }
172
173 func (conn *Conn) CollectionDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Collection, error) {
174         ep := arvados.EndpointCollectionDelete
175         var resp arvados.Collection
176         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
177         return resp, err
178 }
179
180 func (conn *Conn) CollectionTrash(ctx context.Context, options arvados.DeleteOptions) (arvados.Collection, error) {
181         ep := arvados.EndpointCollectionTrash
182         var resp arvados.Collection
183         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
184         return resp, err
185 }
186
187 func (conn *Conn) CollectionUntrash(ctx context.Context, options arvados.UntrashOptions) (arvados.Collection, error) {
188         ep := arvados.EndpointCollectionUntrash
189         var resp arvados.Collection
190         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
191         return resp, err
192 }
193
194 func (conn *Conn) ContainerCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Container, error) {
195         ep := arvados.EndpointContainerCreate
196         var resp arvados.Container
197         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
198         return resp, err
199 }
200
201 func (conn *Conn) ContainerUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Container, error) {
202         ep := arvados.EndpointContainerUpdate
203         var resp arvados.Container
204         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
205         return resp, err
206 }
207
208 func (conn *Conn) ContainerGet(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
209         ep := arvados.EndpointContainerGet
210         var resp arvados.Container
211         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
212         return resp, err
213 }
214
215 func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
216         ep := arvados.EndpointContainerList
217         var resp arvados.ContainerList
218         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
219         return resp, err
220 }
221
222 func (conn *Conn) ContainerDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Container, error) {
223         ep := arvados.EndpointContainerDelete
224         var resp arvados.Container
225         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
226         return resp, err
227 }
228
229 func (conn *Conn) ContainerLock(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
230         ep := arvados.EndpointContainerLock
231         var resp arvados.Container
232         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
233         return resp, err
234 }
235
236 func (conn *Conn) ContainerUnlock(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
237         ep := arvados.EndpointContainerUnlock
238         var resp arvados.Container
239         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
240         return resp, err
241 }
242
243 func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
244         ep := arvados.EndpointSpecimenCreate
245         var resp arvados.Specimen
246         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
247         return resp, err
248 }
249
250 func (conn *Conn) SpecimenUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Specimen, error) {
251         ep := arvados.EndpointSpecimenUpdate
252         var resp arvados.Specimen
253         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
254         return resp, err
255 }
256
257 func (conn *Conn) SpecimenGet(ctx context.Context, options arvados.GetOptions) (arvados.Specimen, error) {
258         ep := arvados.EndpointSpecimenGet
259         var resp arvados.Specimen
260         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
261         return resp, err
262 }
263
264 func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
265         ep := arvados.EndpointSpecimenList
266         var resp arvados.SpecimenList
267         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
268         return resp, err
269 }
270
271 func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Specimen, error) {
272         ep := arvados.EndpointSpecimenDelete
273         var resp arvados.Specimen
274         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
275         return resp, err
276 }
277
278 func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
279         ep := arvados.EndpointAPIClientAuthorizationCurrent
280         var resp arvados.APIClientAuthorization
281         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
282         return resp, err
283 }