Skip to content

Commit fbaf768

Browse files
authored
Merge pull request #2122 from inteon/configurable_http_client
⚠️ make *http.Client configurable and use/ share the same http.Client in the default configuration
2 parents 81199b9 + 4fd4f6e commit fbaf768

18 files changed

+161
-54
lines changed

pkg/cache/cache.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package cache
1919
import (
2020
"context"
2121
"fmt"
22+
"net/http"
2223
"reflect"
2324
"time"
2425

@@ -108,6 +109,9 @@ type SelectorsByObject map[client.Object]ObjectSelector
108109

109110
// Options are the optional arguments for creating a new InformersMap object.
110111
type Options struct {
112+
// HTTPClient is the http client to use for the REST client
113+
HTTPClient *http.Client
114+
111115
// Scheme is the scheme to use for mapping objects to GroupVersionKinds
112116
Scheme *runtime.Scheme
113117

@@ -184,6 +188,7 @@ func New(config *rest.Config, opts Options) (Cache, error) {
184188
return &informerCache{
185189
scheme: opts.Scheme,
186190
Informers: internal.NewInformers(config, &internal.InformersOpts{
191+
HTTPClient: opts.HTTPClient,
187192
Scheme: opts.Scheme,
188193
Mapper: opts.Mapper,
189194
ResyncPeriod: *opts.Resync,
@@ -414,6 +419,18 @@ func combineTransform(inherited, current toolscache.TransformFunc) toolscache.Tr
414419
}
415420

416421
func defaultOpts(config *rest.Config, opts Options) (Options, error) {
422+
logger := log.WithName("setup")
423+
424+
// Use the rest HTTP client for the provided config if unset
425+
if opts.HTTPClient == nil {
426+
var err error
427+
opts.HTTPClient, err = rest.HTTPClientFor(config)
428+
if err != nil {
429+
logger.Error(err, "Failed to get HTTP client")
430+
return opts, fmt.Errorf("could not create HTTP client from config")
431+
}
432+
}
433+
417434
// Use the default Kubernetes Scheme if unset
418435
if opts.Scheme == nil {
419436
opts.Scheme = scheme.Scheme
@@ -422,9 +439,9 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
422439
// Construct a new Mapper if unset
423440
if opts.Mapper == nil {
424441
var err error
425-
opts.Mapper, err = apiutil.NewDiscoveryRESTMapper(config)
442+
opts.Mapper, err = apiutil.NewDiscoveryRESTMapper(config, opts.HTTPClient)
426443
if err != nil {
427-
log.WithName("setup").Error(err, "Failed to get API Group-Resources")
444+
logger.Error(err, "Failed to get API Group-Resources")
428445
return opts, fmt.Errorf("could not create RESTMapper from config")
429446
}
430447
}

pkg/cache/informer_cache_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ var _ = Describe("informerCache", func() {
3131
It("should not require LeaderElection", func() {
3232
cfg := &rest.Config{}
3333

34-
mapper, err := apiutil.NewDynamicRESTMapper(cfg, apiutil.WithLazyDiscovery)
34+
httpClient, err := rest.HTTPClientFor(cfg)
35+
Expect(err).ToNot(HaveOccurred())
36+
mapper, err := apiutil.NewDynamicRESTMapper(cfg, httpClient, apiutil.WithLazyDiscovery)
3537
Expect(err).ToNot(HaveOccurred())
3638

3739
c, err := cache.New(cfg, cache.Options{Mapper: mapper})

pkg/cache/internal/informers.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"fmt"
2222
"math/rand"
23+
"net/http"
2324
"sync"
2425
"time"
2526

@@ -44,6 +45,7 @@ func init() {
4445

4546
// InformersOpts configures an InformerMap.
4647
type InformersOpts struct {
48+
HTTPClient *http.Client
4749
Scheme *runtime.Scheme
4850
Mapper meta.RESTMapper
4951
ResyncPeriod time.Duration
@@ -62,9 +64,10 @@ type InformersOptsByGVK struct {
6264
// NewInformers creates a new InformersMap that can create informers under the hood.
6365
func NewInformers(config *rest.Config, options *InformersOpts) *Informers {
6466
return &Informers{
65-
config: config,
66-
scheme: options.Scheme,
67-
mapper: options.Mapper,
67+
config: config,
68+
httpClient: options.HTTPClient,
69+
scheme: options.Scheme,
70+
mapper: options.Mapper,
6871
tracker: tracker{
6972
Structured: make(map[schema.GroupVersionKind]*Cache),
7073
Unstructured: make(map[schema.GroupVersionKind]*Cache),
@@ -99,6 +102,9 @@ type tracker struct {
99102
// Informers create and caches Informers for (runtime.Object, schema.GroupVersionKind) pairs.
100103
// It uses a standard parameter codec constructed based on the given generated Scheme.
101104
type Informers struct {
105+
// httpClient is used to create a new REST client
106+
httpClient *http.Client
107+
102108
// scheme maps runtime.Objects to GroupVersionKinds
103109
scheme *runtime.Scheme
104110

@@ -364,7 +370,7 @@ func (ip *Informers) makeListWatcher(gvk schema.GroupVersionKind, obj runtime.Ob
364370
// we should remove it and use the one that the dynamic client sets for us.
365371
cfg := rest.CopyConfig(ip.config)
366372
cfg.NegotiatedSerializer = nil
367-
dynamicClient, err := dynamic.NewForConfig(cfg)
373+
dynamicClient, err := dynamic.NewForConfigAndClient(cfg, ip.httpClient)
368374
if err != nil {
369375
return nil, err
370376
}
@@ -394,7 +400,7 @@ func (ip *Informers) makeListWatcher(gvk schema.GroupVersionKind, obj runtime.Ob
394400
cfg.NegotiatedSerializer = nil
395401

396402
// Grab the metadata metadataClient.
397-
metadataClient, err := metadata.NewForConfig(cfg)
403+
metadataClient, err := metadata.NewForConfigAndClient(cfg, ip.httpClient)
398404
if err != nil {
399405
return nil, err
400406
}
@@ -435,7 +441,7 @@ func (ip *Informers) makeListWatcher(gvk schema.GroupVersionKind, obj runtime.Ob
435441
// Structured.
436442
//
437443
default:
438-
client, err := apiutil.RESTClientForGVK(gvk, false, ip.config, ip.codecs)
444+
client, err := apiutil.RESTClientForGVK(gvk, false, ip.config, ip.codecs, ip.httpClient)
439445
if err != nil {
440446
return nil, err
441447
}

pkg/client/apiutil/apimachinery.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package apiutil
2222
import (
2323
"errors"
2424
"fmt"
25+
"net/http"
2526
"reflect"
2627
"sync"
2728

@@ -61,9 +62,13 @@ func AddToProtobufScheme(addToScheme func(*runtime.Scheme) error) error {
6162

6263
// NewDiscoveryRESTMapper constructs a new RESTMapper based on discovery
6364
// information fetched by a new client with the given config.
64-
func NewDiscoveryRESTMapper(c *rest.Config) (meta.RESTMapper, error) {
65+
func NewDiscoveryRESTMapper(c *rest.Config, httpClient *http.Client) (meta.RESTMapper, error) {
66+
if httpClient == nil {
67+
panic("httpClient must not be nil")
68+
}
69+
6570
// Get a mapper
66-
dc, err := discovery.NewDiscoveryClientForConfig(c)
71+
dc, err := discovery.NewDiscoveryClientForConfigAndClient(c, httpClient)
6772
if err != nil {
6873
return nil, err
6974
}
@@ -150,8 +155,11 @@ func GVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersi
150155
// RESTClientForGVK constructs a new rest.Interface capable of accessing the resource associated
151156
// with the given GroupVersionKind. The REST client will be configured to use the negotiated serializer from
152157
// baseConfig, if set, otherwise a default serializer will be set.
153-
func RESTClientForGVK(gvk schema.GroupVersionKind, isUnstructured bool, baseConfig *rest.Config, codecs serializer.CodecFactory) (rest.Interface, error) {
154-
return rest.RESTClientFor(createRestConfig(gvk, isUnstructured, baseConfig, codecs))
158+
func RESTClientForGVK(gvk schema.GroupVersionKind, isUnstructured bool, baseConfig *rest.Config, codecs serializer.CodecFactory, httpClient *http.Client) (rest.Interface, error) {
159+
if httpClient == nil {
160+
panic("httpClient must not be nil")
161+
}
162+
return rest.RESTClientForConfigAndClient(createRestConfig(gvk, isUnstructured, baseConfig, codecs), httpClient)
155163
}
156164

157165
// createRestConfig copies the base config and updates needed fields for a new rest config.

pkg/client/apiutil/dynamicrestmapper.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package apiutil
1818

1919
import (
20+
"net/http"
2021
"sync"
2122
"sync/atomic"
2223

@@ -75,8 +76,12 @@ func WithCustomMapper(newMapper func() (meta.RESTMapper, error)) DynamicRESTMapp
7576
// NewDynamicRESTMapper returns a dynamic RESTMapper for cfg. The dynamic
7677
// RESTMapper dynamically discovers resource types at runtime. opts
7778
// configure the RESTMapper.
78-
func NewDynamicRESTMapper(cfg *rest.Config, opts ...DynamicRESTMapperOption) (meta.RESTMapper, error) {
79-
client, err := discovery.NewDiscoveryClientForConfig(cfg)
79+
func NewDynamicRESTMapper(cfg *rest.Config, httpClient *http.Client, opts ...DynamicRESTMapperOption) (meta.RESTMapper, error) {
80+
if httpClient == nil {
81+
panic("httpClient must not be nil")
82+
}
83+
84+
client, err := discovery.NewDiscoveryClientForConfigAndClient(cfg, httpClient)
8085
if err != nil {
8186
return nil, err
8287
}

pkg/client/apiutil/dynamicrestmapper_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"golang.org/x/time/rate"
2828
"k8s.io/apimachinery/pkg/api/meta"
2929
"k8s.io/apimachinery/pkg/runtime/schema"
30+
"k8s.io/client-go/rest"
3031
)
3132

3233
var (
@@ -50,8 +51,10 @@ var _ = Describe("Dynamic REST Mapper", func() {
5051
baseMapper.Add(targetGVK, meta.RESTScopeNamespace)
5152
}
5253

54+
httpClient, err := rest.HTTPClientFor(cfg)
55+
Expect(err).ToNot(HaveOccurred())
5356
lim = rate.NewLimiter(rate.Limit(5), 5)
54-
mapper, err = NewDynamicRESTMapper(cfg, WithLimiter(lim), WithCustomMapper(func() (meta.RESTMapper, error) {
57+
mapper, err = NewDynamicRESTMapper(cfg, httpClient, WithLimiter(lim), WithCustomMapper(func() (meta.RESTMapper, error) {
5558
baseMapper := meta.NewDefaultRESTMapper(nil)
5659
addToMapper(baseMapper)
5760

@@ -150,7 +153,9 @@ var _ = Describe("Dynamic REST Mapper", func() {
150153
var err error
151154
var failedOnce bool
152155
mockErr := fmt.Errorf("mock failed once")
153-
mapper, err = NewDynamicRESTMapper(cfg, WithLazyDiscovery, WithCustomMapper(func() (meta.RESTMapper, error) {
156+
httpClient, err := rest.HTTPClientFor(cfg)
157+
Expect(err).ToNot(HaveOccurred())
158+
mapper, err = NewDynamicRESTMapper(cfg, httpClient, WithLazyDiscovery, WithCustomMapper(func() (meta.RESTMapper, error) {
154159
// Make newMapper fail once
155160
if !failedOnce {
156161
failedOnce = true

pkg/client/client.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23+
"net/http"
2324
"strings"
2425

2526
"k8s.io/apimachinery/pkg/api/meta"
@@ -38,6 +39,9 @@ import (
3839

3940
// Options are creation options for a Client.
4041
type Options struct {
42+
// HTTPClient is the HTTP client to use for requests.
43+
HTTPClient *http.Client
44+
4145
// Scheme, if provided, will be used to map go structs to GroupVersionKinds
4246
Scheme *runtime.Scheme
4347

@@ -116,6 +120,15 @@ func newClient(config *rest.Config, options Options) (*client, error) {
116120
)
117121
}
118122

123+
// Use the rest HTTP client for the provided config if unset
124+
if options.HTTPClient == nil {
125+
var err error
126+
options.HTTPClient, err = rest.HTTPClientFor(config)
127+
if err != nil {
128+
return nil, err
129+
}
130+
}
131+
119132
// Init a scheme if none provided
120133
if options.Scheme == nil {
121134
options.Scheme = scheme.Scheme
@@ -124,23 +137,24 @@ func newClient(config *rest.Config, options Options) (*client, error) {
124137
// Init a Mapper if none provided
125138
if options.Mapper == nil {
126139
var err error
127-
options.Mapper, err = apiutil.NewDynamicRESTMapper(config)
140+
options.Mapper, err = apiutil.NewDynamicRESTMapper(config, options.HTTPClient)
128141
if err != nil {
129142
return nil, err
130143
}
131144
}
132145

133146
resources := &clientRestResources{
134-
config: config,
135-
scheme: options.Scheme,
136-
mapper: options.Mapper,
137-
codecs: serializer.NewCodecFactory(options.Scheme),
147+
httpClient: options.HTTPClient,
148+
config: config,
149+
scheme: options.Scheme,
150+
mapper: options.Mapper,
151+
codecs: serializer.NewCodecFactory(options.Scheme),
138152

139153
structuredResourceByType: make(map[schema.GroupVersionKind]*resourceMeta),
140154
unstructuredResourceByType: make(map[schema.GroupVersionKind]*resourceMeta),
141155
}
142156

143-
rawMetaClient, err := metadata.NewForConfig(config)
157+
rawMetaClient, err := metadata.NewForConfigAndClient(config, options.HTTPClient)
144158
if err != nil {
145159
return nil, fmt.Errorf("unable to construct metadata-only client for use as part of client: %w", err)
146160
}

pkg/client/client_rest_resources.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package client
1818

1919
import (
20+
"net/http"
2021
"strings"
2122
"sync"
2223

@@ -32,6 +33,9 @@ import (
3233

3334
// clientRestResources creates and stores rest clients and metadata for Kubernetes types.
3435
type clientRestResources struct {
36+
// httpClient is the http client to use for requests
37+
httpClient *http.Client
38+
3539
// config is the rest.Config to talk to an apiserver
3640
config *rest.Config
3741

@@ -59,7 +63,7 @@ func (c *clientRestResources) newResource(gvk schema.GroupVersionKind, isList, i
5963
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
6064
}
6165

62-
client, err := apiutil.RESTClientForGVK(gvk, isUnstructured, c.config, c.codecs)
66+
client, err := apiutil.RESTClientForGVK(gvk, isUnstructured, c.config, c.codecs, c.httpClient)
6367
if err != nil {
6468
return nil, err
6569
}

0 commit comments

Comments
 (0)