Skip to content

Commit 9e77da0

Browse files
authored
[v0.14] Reuse transports with same settings for Fleet CLI (#4410)
1 parent 5b6fbb9 commit 9e77da0

File tree

3 files changed

+79
-34
lines changed

3 files changed

+79
-34
lines changed

internal/bundlereader/auth.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ type Auth struct {
1919
BasicHTTP bool `json:"basicHTTP,omitempty"`
2020
}
2121

22+
func toByte(v bool) byte {
23+
if v {
24+
return byte(1)
25+
}
26+
return byte(0)
27+
}
28+
2229
func ReadHelmAuthFromSecret(ctx context.Context, c client.Reader, req types.NamespacedName) (Auth, error) {
2330
if req.Name == "" {
2431
return Auth{}, nil

internal/bundlereader/charturl.go

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@ package bundlereader
22

33
import (
44
"context"
5+
"crypto/sha256"
56
"crypto/tls"
67
"crypto/x509"
8+
"encoding/binary"
9+
"encoding/hex"
710
"errors"
811
"fmt"
912
"io"
1013
"net/http"
1114
"net/url"
1215
"strings"
16+
"sync"
17+
"time"
1318

1419
"github.com/Masterminds/semver/v3"
1520
fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
@@ -22,6 +27,16 @@ import (
2227
"oras.land/oras-go/v2/registry/remote/errcode"
2328
)
2429

30+
const (
31+
// safety timeout to prevent unbounded requests
32+
httpClientTimeout = 5 * time.Minute
33+
)
34+
35+
var (
36+
transportsCache = map[string]http.RoundTripper{}
37+
transportsCacheMutex sync.RWMutex
38+
)
39+
2540
// ChartVersion returns the version of the helm chart from a helm repo server, by
2641
// inspecting the repo's index.yaml
2742
func ChartVersion(location fleet.HelmOptions, a Auth) (string, error) {
@@ -234,25 +249,69 @@ func GetOCITag(r *remote.Repository, v string) (string, error) {
234249
}
235250

236251
func getHTTPClient(auth Auth) *http.Client {
237-
client := &http.Client{}
252+
return &http.Client{
253+
Transport: transportForAuth(auth.InsecureSkipVerify, auth.CABundle),
254+
Timeout: httpClientTimeout,
255+
}
256+
}
257+
258+
func transportHash(insecureSkipVerify bool, caBundle []byte) string {
259+
hash := sha256.New()
260+
261+
// Write a length prefix for every field to avoid collisions
262+
lenBuf := make([]byte, 8)
263+
writeField := func(data []byte) {
264+
binary.LittleEndian.PutUint64(lenBuf, uint64(len(data)))
265+
hash.Write(lenBuf)
266+
hash.Write(data)
267+
}
268+
269+
for _, v := range [][]byte{ // values to hash
270+
caBundle,
271+
{toByte(insecureSkipVerify)},
272+
} {
273+
writeField(v)
274+
}
238275

276+
return hex.EncodeToString(hash.Sum(nil))
277+
}
278+
279+
func transportForAuth(insecureSkipVerify bool, caBundle []byte) http.RoundTripper {
280+
// We don't need the full hash
281+
hash := transportHash(insecureSkipVerify, caBundle)
282+
283+
// Fast path: valid transport already exists
284+
transportsCacheMutex.RLock()
285+
rt, ok := transportsCache[hash]
286+
transportsCacheMutex.RUnlock()
287+
if ok {
288+
return rt
289+
}
290+
291+
transportsCacheMutex.Lock()
292+
defer transportsCacheMutex.Unlock()
293+
294+
// Check again using write lock
295+
if rt, ok := transportsCache[hash]; ok {
296+
return rt
297+
}
298+
299+
// Create new transport
239300
transport := http.DefaultTransport.(*http.Transport).Clone()
240301
transport.TLSClientConfig = &tls.Config{
241-
InsecureSkipVerify: auth.InsecureSkipVerify, // nolint:gosec
302+
InsecureSkipVerify: insecureSkipVerify, //nolint:gosec
242303
}
243-
244-
if auth.CABundle != nil {
304+
if caBundle != nil {
245305
pool, err := x509.SystemCertPool()
246306
if err != nil {
247307
pool = x509.NewCertPool()
248308
}
249-
pool.AppendCertsFromPEM(auth.CABundle)
309+
pool.AppendCertsFromPEM(caBundle)
250310

251311
transport.TLSClientConfig.RootCAs = pool
252312
transport.TLSClientConfig.MinVersion = tls.VersionTLS12
253313
}
254314

255-
client.Transport = transport
256-
257-
return client
315+
transportsCache[hash] = transport
316+
return transport
258317
}

internal/bundlereader/loaddirectory.go

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ package bundlereader
33
import (
44
"bufio"
55
"context"
6-
"crypto/tls"
7-
"crypto/x509"
86
"encoding/base64"
97
"fmt"
108
"io/fs"
@@ -179,7 +177,7 @@ func loadDirectory(ctx context.Context, opts loadOpts, dir directory) ([]fleet.B
179177
if opts.compress || !utf8.Valid(data) {
180178
content, err := content.Base64GZ(data)
181179
if err != nil {
182-
return nil, err
180+
return nil, fmt.Errorf("decoding compressed base64 data: %w", err)
183181
}
184182
r.Content = content
185183
r.Encoding = "base64+gz"
@@ -211,7 +209,7 @@ func GetContent(ctx context.Context, base, source, version string, auth Auth, di
211209
if hasOCIURL.MatchString(source) {
212210
source, err = downloadOCIChart(source, version, temp, auth)
213211
if err != nil {
214-
return nil, err
212+
return nil, fmt.Errorf("downloading OCI chart from %q: %w", orgSource, err)
215213
}
216214
}
217215

@@ -275,7 +273,7 @@ func GetContent(ctx context.Context, base, source, version string, auth Auth, di
275273
}
276274

277275
if _, err := client.Get(ctx, req); err != nil {
278-
return nil, err
276+
return nil, fmt.Errorf("retrieving file from %q: %w", source, err)
279277
}
280278

281279
files := map[string][]byte{}
@@ -312,7 +310,7 @@ func GetContent(ctx context.Context, base, source, version string, auth Auth, di
312310
// try to update possible dependencies.
313311
if !disableDepsUpdate && helmupdater.ChartYAMLExists(path) {
314312
if err = helmupdater.UpdateHelmDependencies(path); err != nil {
315-
return err
313+
return fmt.Errorf("updating helm dependencies: %w", err)
316314
}
317315
}
318316
// Skip .fleetignore'd and hidden directories
@@ -432,7 +430,7 @@ func downloadOCIChart(name, version, path string, auth Auth) (string, error) {
432430

433431
func newHttpGetter(auth Auth) *getter.HttpGetter {
434432
httpGetter := &getter.HttpGetter{
435-
Client: &http.Client{},
433+
Client: getHTTPClient(auth),
436434
}
437435

438436
if auth.Username != "" && auth.Password != "" {
@@ -441,25 +439,6 @@ func newHttpGetter(auth Auth) *getter.HttpGetter {
441439
httpGetter.Header = header
442440
}
443441

444-
transport := http.DefaultTransport.(*http.Transport).Clone()
445-
if auth.CABundle != nil {
446-
pool, err := x509.SystemCertPool()
447-
if err != nil {
448-
pool = x509.NewCertPool()
449-
}
450-
pool.AppendCertsFromPEM(auth.CABundle)
451-
transport.TLSClientConfig = &tls.Config{
452-
RootCAs: pool,
453-
MinVersion: tls.VersionTLS12,
454-
InsecureSkipVerify: auth.InsecureSkipVerify, // nolint:gosec
455-
}
456-
} else if auth.InsecureSkipVerify {
457-
transport.TLSClientConfig = &tls.Config{
458-
InsecureSkipVerify: auth.InsecureSkipVerify, // nolint:gosec
459-
}
460-
}
461-
httpGetter.Client.Transport = transport
462-
463442
return httpGetter
464443
}
465444

0 commit comments

Comments
 (0)