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