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