Skip to content

Commit

Permalink
make *http.Client configurable
Browse files Browse the repository at this point in the history
Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>
  • Loading branch information
inteon committed Jan 24, 2023
1 parent 613648e commit 409b067
Show file tree
Hide file tree
Showing 18 changed files with 168 additions and 52 deletions.
17 changes: 16 additions & 1 deletion pkg/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package cache
import (
"context"
"fmt"
"net/http"
"reflect"
"time"

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

// Options are the optional arguments for creating a new InformersMap object.
type Options struct {
// HTTPClient is the http client to use for the REST client
HTTPClient *http.Client

// Scheme is the scheme to use for mapping objects to GroupVersionKinds
Scheme *runtime.Scheme

Expand Down Expand Up @@ -184,6 +188,7 @@ func New(config *rest.Config, opts Options) (Cache, error) {
return &informerCache{
scheme: opts.Scheme,
Informers: internal.NewInformers(config, &internal.InformersOpts{
HTTPClient: opts.HTTPClient,
Scheme: opts.Scheme,
Mapper: opts.Mapper,
ResyncPeriod: *opts.Resync,
Expand Down Expand Up @@ -414,6 +419,16 @@ func combineTransform(inherited, current toolscache.TransformFunc) toolscache.Tr
}

func defaultOpts(config *rest.Config, opts Options) (Options, error) {
// Use the rest HTTP client for the provided config if unset
if opts.HTTPClient == nil {
var err error
opts.HTTPClient, err = rest.HTTPClientFor(config)
if err != nil {
log.WithName("setup").Error(err, "Failed to get HTTP client")
return opts, fmt.Errorf("could not create HTTP client from config")
}
}

// Use the default Kubernetes Scheme if unset
if opts.Scheme == nil {
opts.Scheme = scheme.Scheme
Expand All @@ -422,7 +437,7 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
// Construct a new Mapper if unset
if opts.Mapper == nil {
var err error
opts.Mapper, err = apiutil.NewDiscoveryRESTMapper(config)
opts.Mapper, err = apiutil.NewDiscoveryRESTMapper(config, opts.HTTPClient)
if err != nil {
log.WithName("setup").Error(err, "Failed to get API Group-Resources")
return opts, fmt.Errorf("could not create RESTMapper from config")
Expand Down
2 changes: 1 addition & 1 deletion pkg/cache/informer_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ var _ = Describe("informerCache", func() {
It("should not require LeaderElection", func() {
cfg := &rest.Config{}

mapper, err := apiutil.NewDynamicRESTMapper(cfg, apiutil.WithLazyDiscovery)
mapper, err := apiutil.NewDynamicRESTMapper(cfg, nil, apiutil.WithLazyDiscovery)
Expect(err).ToNot(HaveOccurred())

c, err := cache.New(cfg, cache.Options{Mapper: mapper})
Expand Down
18 changes: 12 additions & 6 deletions pkg/cache/internal/informers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"fmt"
"math/rand"
"net/http"
"sync"
"time"

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

// InformersOpts configures an InformerMap.
type InformersOpts struct {
HTTPClient *http.Client
Scheme *runtime.Scheme
Mapper meta.RESTMapper
ResyncPeriod time.Duration
Expand All @@ -62,9 +64,10 @@ type InformersOptsByGVK struct {
// NewInformers creates a new InformersMap that can create informers under the hood.
func NewInformers(config *rest.Config, options *InformersOpts) *Informers {
return &Informers{
config: config,
scheme: options.Scheme,
mapper: options.Mapper,
config: config,
httpClient: options.HTTPClient,
scheme: options.Scheme,
mapper: options.Mapper,
tracker: tracker{
Structured: make(map[schema.GroupVersionKind]*Cache),
Unstructured: make(map[schema.GroupVersionKind]*Cache),
Expand Down Expand Up @@ -99,6 +102,9 @@ type tracker struct {
// Informers create and caches Informers for (runtime.Object, schema.GroupVersionKind) pairs.
// It uses a standard parameter codec constructed based on the given generated Scheme.
type Informers struct {
// httpClient is used to create a new REST client
httpClient *http.Client

// scheme maps runtime.Objects to GroupVersionKinds
scheme *runtime.Scheme

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

// Grab the metadata metadataClient.
metadataClient, err := metadata.NewForConfig(cfg)
metadataClient, err := metadata.NewForConfigAndClient(cfg, ip.httpClient)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -435,7 +441,7 @@ func (ip *Informers) makeListWatcher(gvk schema.GroupVersionKind, obj runtime.Ob
// Structured.
//
default:
client, err := apiutil.RESTClientForGVK(gvk, false, ip.config, ip.codecs)
client, err := apiutil.RESTClientForGVK(gvk, false, ip.config, ip.codecs, ip.httpClient)
if err != nil {
return nil, err
}
Expand Down
31 changes: 27 additions & 4 deletions pkg/client/apiutil/apimachinery.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package apiutil
import (
"errors"
"fmt"
"net/http"
"reflect"
"sync"

Expand Down Expand Up @@ -60,9 +61,13 @@ func AddToProtobufScheme(addToScheme func(*runtime.Scheme) error) error {

// NewDiscoveryRESTMapper constructs a new RESTMapper based on discovery
// information fetched by a new client with the given config.
func NewDiscoveryRESTMapper(c *rest.Config) (meta.RESTMapper, error) {
func NewDiscoveryRESTMapper(c *rest.Config, httpClient *http.Client) (meta.RESTMapper, error) {
if err := DefaultHTTPClient(c, &httpClient); err != nil {
return nil, err
}

// Get a mapper
dc, err := discovery.NewDiscoveryClientForConfig(c)
dc, err := discovery.NewDiscoveryClientForConfigAndClient(c, httpClient)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -146,11 +151,29 @@ func GVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersi
return gvks[0], nil
}

// DefaultHTTPClient is a helper function that sets the HTTP client based on a rest config if it is not already set.
func DefaultHTTPClient(config *rest.Config, httpClient **http.Client) error {
if httpClient == nil {
panic("httpClient must not be nil")
}

if *httpClient != nil {
return nil
}

var err error
*httpClient, err = rest.HTTPClientFor(config)
return err
}

// RESTClientForGVK constructs a new rest.Interface capable of accessing the resource associated
// with the given GroupVersionKind. The REST client will be configured to use the negotiated serializer from
// baseConfig, if set, otherwise a default serializer will be set.
func RESTClientForGVK(gvk schema.GroupVersionKind, isUnstructured bool, baseConfig *rest.Config, codecs serializer.CodecFactory) (rest.Interface, error) {
return rest.RESTClientFor(createRestConfig(gvk, isUnstructured, baseConfig, codecs))
func RESTClientForGVK(gvk schema.GroupVersionKind, isUnstructured bool, baseConfig *rest.Config, codecs serializer.CodecFactory, httpClient *http.Client) (rest.Interface, error) {
if err := DefaultHTTPClient(baseConfig, &httpClient); err != nil {
return nil, err
}
return rest.RESTClientForConfigAndClient(createRestConfig(gvk, isUnstructured, baseConfig, codecs), httpClient)
}

// serializerWithDecodedGVK is a CodecFactory that overrides the DecoderToVersion of a WithoutConversionCodecFactory
Expand Down
9 changes: 7 additions & 2 deletions pkg/client/apiutil/dynamicrestmapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package apiutil

import (
"net/http"
"sync"
"sync/atomic"

Expand Down Expand Up @@ -75,8 +76,12 @@ func WithCustomMapper(newMapper func() (meta.RESTMapper, error)) DynamicRESTMapp
// NewDynamicRESTMapper returns a dynamic RESTMapper for cfg. The dynamic
// RESTMapper dynamically discovers resource types at runtime. opts
// configure the RESTMapper.
func NewDynamicRESTMapper(cfg *rest.Config, opts ...DynamicRESTMapperOption) (meta.RESTMapper, error) {
client, err := discovery.NewDiscoveryClientForConfig(cfg)
func NewDynamicRESTMapper(cfg *rest.Config, httpClient *http.Client, opts ...DynamicRESTMapperOption) (meta.RESTMapper, error) {
if err := DefaultHTTPClient(cfg, &httpClient); err != nil {
return nil, err
}

client, err := discovery.NewDiscoveryClientForConfigAndClient(cfg, httpClient)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/client/apiutil/dynamicrestmapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ var _ = Describe("Dynamic REST Mapper", func() {
}

lim = rate.NewLimiter(rate.Limit(5), 5)
mapper, err = NewDynamicRESTMapper(cfg, WithLimiter(lim), WithCustomMapper(func() (meta.RESTMapper, error) {
mapper, err = NewDynamicRESTMapper(cfg, nil, WithLimiter(lim), WithCustomMapper(func() (meta.RESTMapper, error) {
baseMapper := meta.NewDefaultRESTMapper(nil)
addToMapper(baseMapper)

Expand Down Expand Up @@ -150,7 +150,7 @@ var _ = Describe("Dynamic REST Mapper", func() {
var err error
var failedOnce bool
mockErr := fmt.Errorf("mock failed once")
mapper, err = NewDynamicRESTMapper(cfg, WithLazyDiscovery, WithCustomMapper(func() (meta.RESTMapper, error) {
mapper, err = NewDynamicRESTMapper(cfg, nil, WithLazyDiscovery, WithCustomMapper(func() (meta.RESTMapper, error) {
// Make newMapper fail once
if !failedOnce {
failedOnce = true
Expand Down
26 changes: 20 additions & 6 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"errors"
"fmt"
"net/http"
"strings"

"k8s.io/apimachinery/pkg/api/meta"
Expand Down Expand Up @@ -52,6 +53,9 @@ type WarningHandlerOptions struct {

// Options are creation options for a Client.
type Options struct {
// HTTPClient is the http client to use for the REST client
HTTPClient *http.Client

// Scheme, if provided, will be used to map go structs to GroupVersionKinds
Scheme *runtime.Scheme

Expand Down Expand Up @@ -98,6 +102,15 @@ func newClient(config *rest.Config, options Options) (*client, error) {
)
}

// Use the rest HTTP client for the provided config if unset
if options.HTTPClient == nil {
var err error
options.HTTPClient, err = rest.HTTPClientFor(config)
if err != nil {
return nil, err
}
}

// Init a scheme if none provided
if options.Scheme == nil {
options.Scheme = scheme.Scheme
Expand All @@ -106,23 +119,24 @@ func newClient(config *rest.Config, options Options) (*client, error) {
// Init a Mapper if none provided
if options.Mapper == nil {
var err error
options.Mapper, err = apiutil.NewDynamicRESTMapper(config)
options.Mapper, err = apiutil.NewDynamicRESTMapper(config, options.HTTPClient)
if err != nil {
return nil, err
}
}

resources := &clientRestResources{
config: config,
scheme: options.Scheme,
mapper: options.Mapper,
codecs: serializer.NewCodecFactory(options.Scheme),
httpClient: options.HTTPClient,
config: config,
scheme: options.Scheme,
mapper: options.Mapper,
codecs: serializer.NewCodecFactory(options.Scheme),

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

rawMetaClient, err := metadata.NewForConfig(config)
rawMetaClient, err := metadata.NewForConfigAndClient(config, options.HTTPClient)
if err != nil {
return nil, fmt.Errorf("unable to construct metadata-only client for use as part of client: %w", err)
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/client/client_rest_resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package client

import (
"net/http"
"strings"
"sync"

Expand All @@ -32,6 +33,9 @@ import (

// clientRestResources creates and stores rest clients and metadata for Kubernetes types.
type clientRestResources struct {
// httpClient is the http client to use for requests
httpClient *http.Client

// config is the rest.Config to talk to an apiserver
config *rest.Config

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

client, err := apiutil.RESTClientForGVK(gvk, isUnstructured, c.config, c.codecs)
client, err := apiutil.RESTClientForGVK(gvk, isUnstructured, c.config, c.codecs, c.httpClient)
if err != nil {
return nil, err
}
Expand Down
12 changes: 11 additions & 1 deletion pkg/client/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,21 @@ import (

// NewWithWatch returns a new WithWatch.
func NewWithWatch(config *rest.Config, options Options) (WithWatch, error) {
// Use the rest HTTP client for the provided config if unset
if options.HTTPClient == nil {
var err error
options.HTTPClient, err = rest.HTTPClientFor(config)
if err != nil {
return nil, err
}
}

client, err := newClient(config, options)
if err != nil {
return nil, err
}
dynamicClient, err := dynamic.NewForConfig(config)

dynamicClient, err := dynamic.NewForConfigAndClient(config, options.HTTPClient)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit 409b067

Please sign in to comment.