k8s.io/client-go@v0.31.1/rest/config_test.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package rest
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"net"
    25  	"net/http"
    26  	"net/url"
    27  	"path/filepath"
    28  	"reflect"
    29  	"strings"
    30  	"testing"
    31  	"time"
    32  
    33  	v1 "k8s.io/api/core/v1"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	"k8s.io/apimachinery/pkg/runtime/schema"
    36  	"k8s.io/client-go/kubernetes/scheme"
    37  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    38  	"k8s.io/client-go/transport"
    39  	"k8s.io/client-go/util/flowcontrol"
    40  
    41  	"github.com/google/go-cmp/cmp"
    42  	fuzz "github.com/google/gofuzz"
    43  	"github.com/stretchr/testify/assert"
    44  )
    45  
    46  func TestIsConfigTransportTLS(t *testing.T) {
    47  	testCases := []struct {
    48  		Config       *Config
    49  		TransportTLS bool
    50  	}{
    51  		{
    52  			Config:       &Config{},
    53  			TransportTLS: false,
    54  		},
    55  		{
    56  			Config: &Config{
    57  				Host: "https://localhost",
    58  			},
    59  			TransportTLS: true,
    60  		},
    61  		{
    62  			Config: &Config{
    63  				Host: "localhost",
    64  				TLSClientConfig: TLSClientConfig{
    65  					CertFile: "foo",
    66  				},
    67  			},
    68  			TransportTLS: true,
    69  		},
    70  		{
    71  			Config: &Config{
    72  				Host: "///:://localhost",
    73  				TLSClientConfig: TLSClientConfig{
    74  					CertFile: "foo",
    75  				},
    76  			},
    77  			TransportTLS: false,
    78  		},
    79  		{
    80  			Config: &Config{
    81  				Host: "1.2.3.4:567",
    82  				TLSClientConfig: TLSClientConfig{
    83  					Insecure: true,
    84  				},
    85  			},
    86  			TransportTLS: true,
    87  		},
    88  	}
    89  	for _, testCase := range testCases {
    90  		if err := SetKubernetesDefaults(testCase.Config); err != nil {
    91  			t.Errorf("setting defaults failed for %#v: %v", testCase.Config, err)
    92  			continue
    93  		}
    94  		useTLS := IsConfigTransportTLS(*testCase.Config)
    95  		if testCase.TransportTLS != useTLS {
    96  			t.Errorf("expected %v for %#v", testCase.TransportTLS, testCase.Config)
    97  		}
    98  	}
    99  }
   100  
   101  func TestSetKubernetesDefaultsUserAgent(t *testing.T) {
   102  	config := &Config{}
   103  	if err := SetKubernetesDefaults(config); err != nil {
   104  		t.Errorf("unexpected error: %v", err)
   105  	}
   106  	if !strings.Contains(config.UserAgent, "kubernetes/") {
   107  		t.Errorf("no user agent set: %#v", config)
   108  	}
   109  }
   110  
   111  func TestAdjustVersion(t *testing.T) {
   112  	assert := assert.New(t)
   113  	assert.Equal("1.2.3", adjustVersion("1.2.3-alpha4"))
   114  	assert.Equal("1.2.3", adjustVersion("1.2.3-alpha"))
   115  	assert.Equal("1.2.3", adjustVersion("1.2.3"))
   116  	assert.Equal("unknown", adjustVersion(""))
   117  }
   118  
   119  func TestAdjustCommit(t *testing.T) {
   120  	assert := assert.New(t)
   121  	assert.Equal("1234567", adjustCommit("1234567890"))
   122  	assert.Equal("123456", adjustCommit("123456"))
   123  	assert.Equal("unknown", adjustCommit(""))
   124  }
   125  
   126  func TestAdjustCommand(t *testing.T) {
   127  	assert := assert.New(t)
   128  	assert.Equal("beans", adjustCommand(filepath.Join("home", "bob", "Downloads", "beans")))
   129  	assert.Equal("beans", adjustCommand(filepath.Join(".", "beans")))
   130  	assert.Equal("beans", adjustCommand("beans"))
   131  	assert.Equal("unknown", adjustCommand(""))
   132  }
   133  
   134  func TestBuildUserAgent(t *testing.T) {
   135  	assert.New(t).Equal(
   136  		"lynx/nicest (beos/itanium) kubernetes/baaaaaaaaad",
   137  		buildUserAgent(
   138  			"lynx", "nicest",
   139  			"beos", "itanium", "baaaaaaaaad"))
   140  }
   141  
   142  // This function untestable since it doesn't accept arguments.
   143  func TestDefaultKubernetesUserAgent(t *testing.T) {
   144  	assert.New(t).Contains(DefaultKubernetesUserAgent(), "kubernetes")
   145  }
   146  
   147  func TestRESTClientRequires(t *testing.T) {
   148  	if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{NegotiatedSerializer: scheme.Codecs}}); err == nil {
   149  		t.Errorf("unexpected non-error")
   150  	}
   151  	if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: &v1.SchemeGroupVersion}}); err == nil {
   152  		t.Errorf("unexpected non-error")
   153  	}
   154  	if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs}}); err != nil {
   155  		t.Errorf("unexpected error: %v", err)
   156  	}
   157  }
   158  
   159  func TestRESTClientLimiter(t *testing.T) {
   160  	testCases := []struct {
   161  		Name    string
   162  		Config  Config
   163  		Limiter flowcontrol.RateLimiter
   164  	}{
   165  		{
   166  			Name:    "with no QPS",
   167  			Config:  Config{},
   168  			Limiter: flowcontrol.NewTokenBucketRateLimiter(5, 10),
   169  		},
   170  		{
   171  			Name:    "with QPS:10",
   172  			Config:  Config{QPS: 10},
   173  			Limiter: flowcontrol.NewTokenBucketRateLimiter(10, 10),
   174  		},
   175  		{
   176  			Name:    "with QPS:-1",
   177  			Config:  Config{QPS: -1},
   178  			Limiter: nil,
   179  		},
   180  		{
   181  			Name: "with RateLimiter",
   182  			Config: Config{
   183  				RateLimiter: flowcontrol.NewTokenBucketRateLimiter(11, 12),
   184  			},
   185  			Limiter: flowcontrol.NewTokenBucketRateLimiter(11, 12),
   186  		},
   187  	}
   188  	for _, testCase := range testCases {
   189  		t.Run("Versioned_"+testCase.Name, func(t *testing.T) {
   190  			config := testCase.Config
   191  			config.Host = "127.0.0.1"
   192  			config.ContentConfig = ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs}
   193  			client, err := RESTClientFor(&config)
   194  			if err != nil {
   195  				t.Fatalf("unexpected error: %v", err)
   196  			}
   197  			if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) {
   198  				t.Fatalf("unexpected rate limiter: %#v, expected %#v at %s", client.rateLimiter, testCase.Limiter, testCase.Name)
   199  			}
   200  		})
   201  		t.Run("Unversioned_"+testCase.Name, func(t *testing.T) {
   202  			config := testCase.Config
   203  			config.Host = "127.0.0.1"
   204  			config.ContentConfig = ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs}
   205  			client, err := UnversionedRESTClientFor(&config)
   206  			if err != nil {
   207  				t.Fatalf("unexpected error: %v", err)
   208  			}
   209  			if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) {
   210  				t.Fatalf("unexpected rate limiter: %#v, expected %#v at %s", client.rateLimiter, testCase.Limiter, testCase.Name)
   211  			}
   212  		})
   213  	}
   214  }
   215  
   216  type fakeLimiter struct {
   217  	FakeSaturation float64
   218  	FakeQPS        float32
   219  }
   220  
   221  func (t *fakeLimiter) TryAccept() bool {
   222  	return true
   223  }
   224  
   225  func (t *fakeLimiter) Saturation() float64 {
   226  	return t.FakeSaturation
   227  }
   228  
   229  func (t *fakeLimiter) QPS() float32 {
   230  	return t.FakeQPS
   231  }
   232  
   233  func (t *fakeLimiter) Wait(ctx context.Context) error {
   234  	return nil
   235  }
   236  
   237  func (t *fakeLimiter) Stop() {}
   238  
   239  func (t *fakeLimiter) Accept() {}
   240  
   241  type fakeCodec struct{}
   242  
   243  func (c *fakeCodec) Decode([]byte, *schema.GroupVersionKind, runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
   244  	return nil, nil, nil
   245  }
   246  
   247  func (c *fakeCodec) Encode(obj runtime.Object, stream io.Writer) error {
   248  	return nil
   249  }
   250  
   251  func (c *fakeCodec) Identifier() runtime.Identifier {
   252  	return runtime.Identifier("fake")
   253  }
   254  
   255  type fakeRoundTripper struct{}
   256  
   257  func (r *fakeRoundTripper) RoundTrip(*http.Request) (*http.Response, error) {
   258  	return nil, nil
   259  }
   260  
   261  var fakeWrapperFunc = func(http.RoundTripper) http.RoundTripper {
   262  	return &fakeRoundTripper{}
   263  }
   264  
   265  type fakeWarningHandler struct{}
   266  
   267  func (f fakeWarningHandler) HandleWarningHeader(code int, agent string, message string) {}
   268  
   269  type fakeNegotiatedSerializer struct{}
   270  
   271  func (n *fakeNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
   272  	return nil
   273  }
   274  
   275  func (n *fakeNegotiatedSerializer) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
   276  	return &fakeCodec{}
   277  }
   278  
   279  func (n *fakeNegotiatedSerializer) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
   280  	return &fakeCodec{}
   281  }
   282  
   283  var fakeDialFunc = func(ctx context.Context, network, addr string) (net.Conn, error) {
   284  	return nil, fakeDialerError
   285  }
   286  
   287  var fakeDialerError = errors.New("fakedialer")
   288  
   289  func fakeProxyFunc(*http.Request) (*url.URL, error) {
   290  	return nil, errors.New("fakeproxy")
   291  }
   292  
   293  type fakeAuthProviderConfigPersister struct{}
   294  
   295  func (fakeAuthProviderConfigPersister) Persist(map[string]string) error {
   296  	return fakeAuthProviderConfigPersisterError
   297  }
   298  
   299  var fakeAuthProviderConfigPersisterError = errors.New("fakeAuthProviderConfigPersisterError")
   300  
   301  func TestAnonymousAuthConfig(t *testing.T) {
   302  	f := fuzz.New().NilChance(0.0).NumElements(1, 1)
   303  	f.Funcs(
   304  		func(r *runtime.Codec, f fuzz.Continue) {
   305  			codec := &fakeCodec{}
   306  			f.Fuzz(codec)
   307  			*r = codec
   308  		},
   309  		func(r *http.RoundTripper, f fuzz.Continue) {
   310  			roundTripper := &fakeRoundTripper{}
   311  			f.Fuzz(roundTripper)
   312  			*r = roundTripper
   313  		},
   314  		func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) {
   315  			*fn = fakeWrapperFunc
   316  		},
   317  		func(fn *transport.WrapperFunc, f fuzz.Continue) {
   318  			*fn = fakeWrapperFunc
   319  		},
   320  		func(r *runtime.NegotiatedSerializer, f fuzz.Continue) {
   321  			serializer := &fakeNegotiatedSerializer{}
   322  			f.Fuzz(serializer)
   323  			*r = serializer
   324  		},
   325  		func(r *flowcontrol.RateLimiter, f fuzz.Continue) {
   326  			limiter := &fakeLimiter{}
   327  			f.Fuzz(limiter)
   328  			*r = limiter
   329  		},
   330  		func(h *WarningHandler, f fuzz.Continue) {
   331  			*h = &fakeWarningHandler{}
   332  		},
   333  		// Authentication does not require fuzzer
   334  		func(r *AuthProviderConfigPersister, f fuzz.Continue) {},
   335  		func(r *clientcmdapi.AuthProviderConfig, f fuzz.Continue) {
   336  			r.Config = map[string]string{}
   337  		},
   338  		func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) {
   339  			*r = fakeDialFunc
   340  		},
   341  		func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
   342  			*r = fakeProxyFunc
   343  		},
   344  		func(r *runtime.Object, f fuzz.Continue) {
   345  			unknown := &runtime.Unknown{}
   346  			f.Fuzz(unknown)
   347  			*r = unknown
   348  		},
   349  	)
   350  	for i := 0; i < 20; i++ {
   351  		original := &Config{}
   352  		f.Fuzz(original)
   353  		actual := AnonymousClientConfig(original)
   354  		expected := *original
   355  
   356  		// this is the list of known security related fields, add to this list if a new field
   357  		// is added to Config, update AnonymousClientConfig to preserve the field otherwise.
   358  		expected.Impersonate = ImpersonationConfig{}
   359  		expected.BearerToken = ""
   360  		expected.BearerTokenFile = ""
   361  		expected.Username = ""
   362  		expected.Password = ""
   363  		expected.AuthProvider = nil
   364  		expected.AuthConfigPersister = nil
   365  		expected.ExecProvider = nil
   366  		expected.TLSClientConfig.CertData = nil
   367  		expected.TLSClientConfig.CertFile = ""
   368  		expected.TLSClientConfig.KeyData = nil
   369  		expected.TLSClientConfig.KeyFile = ""
   370  		expected.Transport = nil
   371  		expected.WrapTransport = nil
   372  
   373  		if actual.Dial != nil {
   374  			_, actualError := actual.Dial(context.Background(), "", "")
   375  			_, expectedError := expected.Dial(context.Background(), "", "")
   376  			if !reflect.DeepEqual(expectedError, actualError) {
   377  				t.Fatalf("AnonymousClientConfig dropped the Dial field")
   378  			}
   379  		}
   380  		actual.Dial = nil
   381  		expected.Dial = nil
   382  
   383  		if actual.Proxy != nil {
   384  			_, actualError := actual.Proxy(nil)
   385  			_, expectedError := expected.Proxy(nil)
   386  			if !reflect.DeepEqual(expectedError, actualError) {
   387  				t.Fatalf("AnonymousClientConfig dropped the Proxy field")
   388  			}
   389  		}
   390  		actual.Proxy = nil
   391  		expected.Proxy = nil
   392  
   393  		if diff := cmp.Diff(*actual, expected); diff != "" {
   394  			t.Fatalf("AnonymousClientConfig dropped unexpected fields, identify whether they are security related or not (-got, +want): %s", diff)
   395  		}
   396  	}
   397  }
   398  
   399  func TestCopyConfig(t *testing.T) {
   400  	f := fuzz.New().NilChance(0.0).NumElements(1, 1)
   401  	f.Funcs(
   402  		func(r *runtime.Codec, f fuzz.Continue) {
   403  			codec := &fakeCodec{}
   404  			f.Fuzz(codec)
   405  			*r = codec
   406  		},
   407  		func(r *http.RoundTripper, f fuzz.Continue) {
   408  			roundTripper := &fakeRoundTripper{}
   409  			f.Fuzz(roundTripper)
   410  			*r = roundTripper
   411  		},
   412  		func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) {
   413  			*fn = fakeWrapperFunc
   414  		},
   415  		func(fn *transport.WrapperFunc, f fuzz.Continue) {
   416  			*fn = fakeWrapperFunc
   417  		},
   418  		func(r *runtime.NegotiatedSerializer, f fuzz.Continue) {
   419  			serializer := &fakeNegotiatedSerializer{}
   420  			f.Fuzz(serializer)
   421  			*r = serializer
   422  		},
   423  		func(r *flowcontrol.RateLimiter, f fuzz.Continue) {
   424  			limiter := &fakeLimiter{}
   425  			f.Fuzz(limiter)
   426  			*r = limiter
   427  		},
   428  		func(h *WarningHandler, f fuzz.Continue) {
   429  			*h = &fakeWarningHandler{}
   430  		},
   431  		func(r *AuthProviderConfigPersister, f fuzz.Continue) {
   432  			*r = fakeAuthProviderConfigPersister{}
   433  		},
   434  		func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) {
   435  			*r = fakeDialFunc
   436  		},
   437  		func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
   438  			*r = fakeProxyFunc
   439  		},
   440  		func(r *runtime.Object, f fuzz.Continue) {
   441  			unknown := &runtime.Unknown{}
   442  			f.Fuzz(unknown)
   443  			*r = unknown
   444  		},
   445  	)
   446  	for i := 0; i < 20; i++ {
   447  		original := &Config{}
   448  		f.Fuzz(original)
   449  		actual := CopyConfig(original)
   450  		expected := *original
   451  
   452  		// this is the list of known risky fields, add to this list if a new field
   453  		// is added to Config, update CopyConfig to preserve the field otherwise.
   454  
   455  		// The DeepEqual cannot handle the func comparison, so we just verify if the
   456  		// function return the expected object.
   457  		if actual.WrapTransport == nil || !reflect.DeepEqual(expected.WrapTransport(nil), &fakeRoundTripper{}) {
   458  			t.Fatalf("CopyConfig dropped the WrapTransport field")
   459  		}
   460  		actual.WrapTransport = nil
   461  		expected.WrapTransport = nil
   462  
   463  		if actual.Dial != nil {
   464  			_, actualError := actual.Dial(context.Background(), "", "")
   465  			_, expectedError := expected.Dial(context.Background(), "", "")
   466  			if !reflect.DeepEqual(expectedError, actualError) {
   467  				t.Fatalf("CopyConfig  dropped the Dial field")
   468  			}
   469  		}
   470  		actual.Dial = nil
   471  		expected.Dial = nil
   472  
   473  		if actual.AuthConfigPersister != nil {
   474  			actualError := actual.AuthConfigPersister.Persist(nil)
   475  			expectedError := expected.AuthConfigPersister.Persist(nil)
   476  			if !reflect.DeepEqual(expectedError, actualError) {
   477  				t.Fatalf("CopyConfig  dropped the Dial field")
   478  			}
   479  		}
   480  		actual.AuthConfigPersister = nil
   481  		expected.AuthConfigPersister = nil
   482  
   483  		if actual.Proxy != nil {
   484  			_, actualError := actual.Proxy(nil)
   485  			_, expectedError := expected.Proxy(nil)
   486  			if !reflect.DeepEqual(expectedError, actualError) {
   487  				t.Fatalf("CopyConfig  dropped the Proxy field")
   488  			}
   489  		}
   490  		actual.Proxy = nil
   491  		expected.Proxy = nil
   492  
   493  		if diff := cmp.Diff(*actual, expected); diff != "" {
   494  			t.Fatalf("CopyConfig  dropped unexpected fields, identify whether they are security related or not (-got, +want): %s", diff)
   495  		}
   496  	}
   497  }
   498  
   499  func TestConfigStringer(t *testing.T) {
   500  	formatBytes := func(b []byte) string {
   501  		// %#v for []byte always pre-pends "[]byte{".
   502  		// %#v for struct with []byte field always pre-pends "[]uint8{".
   503  		return strings.Replace(fmt.Sprintf("%#v", b), "byte", "uint8", 1)
   504  	}
   505  	tests := []struct {
   506  		desc            string
   507  		c               *Config
   508  		expectContent   []string
   509  		prohibitContent []string
   510  	}{
   511  		{
   512  			desc:          "nil config",
   513  			c:             nil,
   514  			expectContent: []string{"<nil>"},
   515  		},
   516  		{
   517  			desc: "non-sensitive config",
   518  			c: &Config{
   519  				Host:      "localhost:8080",
   520  				APIPath:   "v1",
   521  				UserAgent: "gobot",
   522  			},
   523  			expectContent: []string{"localhost:8080", "v1", "gobot"},
   524  		},
   525  		{
   526  			desc: "sensitive config",
   527  			c: &Config{
   528  				Host:        "localhost:8080",
   529  				Username:    "gopher",
   530  				Password:    "g0ph3r",
   531  				BearerToken: "1234567890",
   532  				TLSClientConfig: TLSClientConfig{
   533  					CertFile: "a.crt",
   534  					KeyFile:  "a.key",
   535  					CertData: []byte("fake cert"),
   536  					KeyData:  []byte("fake key"),
   537  				},
   538  				AuthProvider: &clientcmdapi.AuthProviderConfig{
   539  					Config: map[string]string{"secret": "s3cr3t"},
   540  				},
   541  				ExecProvider: &clientcmdapi.ExecConfig{
   542  					Args:   []string{"secret"},
   543  					Env:    []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
   544  					Config: &runtime.Unknown{Raw: []byte("here is some config data")},
   545  				},
   546  			},
   547  			expectContent: []string{
   548  				"localhost:8080",
   549  				"gopher",
   550  				"a.crt",
   551  				"a.key",
   552  				"--- REDACTED ---",
   553  				formatBytes([]byte("--- REDACTED ---")),
   554  				formatBytes([]byte("--- TRUNCATED ---")),
   555  			},
   556  			prohibitContent: []string{
   557  				"g0ph3r",
   558  				"1234567890",
   559  				formatBytes([]byte("fake cert")),
   560  				formatBytes([]byte("fake key")),
   561  				"secret",
   562  				"s3cr3t",
   563  				"here is some config data",
   564  				formatBytes([]byte("super secret password")),
   565  			},
   566  		},
   567  	}
   568  
   569  	for _, tt := range tests {
   570  		t.Run(tt.desc, func(t *testing.T) {
   571  			got := tt.c.String()
   572  			t.Logf("formatted config: %q", got)
   573  
   574  			for _, expect := range tt.expectContent {
   575  				if !strings.Contains(got, expect) {
   576  					t.Errorf("missing expected string %q", expect)
   577  				}
   578  			}
   579  			for _, prohibit := range tt.prohibitContent {
   580  				if strings.Contains(got, prohibit) {
   581  					t.Errorf("found prohibited string %q", prohibit)
   582  				}
   583  			}
   584  		})
   585  	}
   586  }
   587  
   588  func TestConfigSprint(t *testing.T) {
   589  	c := &Config{
   590  		Host:    "localhost:8080",
   591  		APIPath: "v1",
   592  		ContentConfig: ContentConfig{
   593  			AcceptContentTypes: "application/json",
   594  			ContentType:        "application/json",
   595  		},
   596  		Username:    "gopher",
   597  		Password:    "g0ph3r",
   598  		BearerToken: "1234567890",
   599  		Impersonate: ImpersonationConfig{
   600  			UserName: "gopher2",
   601  			UID:      "uid123",
   602  		},
   603  		AuthProvider: &clientcmdapi.AuthProviderConfig{
   604  			Name:   "gopher",
   605  			Config: map[string]string{"secret": "s3cr3t"},
   606  		},
   607  		AuthConfigPersister: fakeAuthProviderConfigPersister{},
   608  		ExecProvider: &clientcmdapi.ExecConfig{
   609  			Command:            "sudo",
   610  			Args:               []string{"secret"},
   611  			Env:                []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
   612  			ProvideClusterInfo: true,
   613  			Config:             &runtime.Unknown{Raw: []byte("super secret password")},
   614  		},
   615  		TLSClientConfig: TLSClientConfig{
   616  			CertFile:   "a.crt",
   617  			KeyFile:    "a.key",
   618  			CertData:   []byte("fake cert"),
   619  			KeyData:    []byte("fake key"),
   620  			NextProtos: []string{"h2", "http/1.1"},
   621  		},
   622  		UserAgent:      "gobot",
   623  		Transport:      &fakeRoundTripper{},
   624  		WrapTransport:  fakeWrapperFunc,
   625  		QPS:            1,
   626  		Burst:          2,
   627  		RateLimiter:    &fakeLimiter{},
   628  		WarningHandler: fakeWarningHandler{},
   629  		Timeout:        3 * time.Second,
   630  		Dial:           fakeDialFunc,
   631  		Proxy:          fakeProxyFunc,
   632  	}
   633  	want := fmt.Sprintf(
   634  		`&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", UID:"uid123", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), ExecProvider:api.ExecConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: "", ProvideClusterInfo: true, Config: runtime.Object(--- REDACTED ---), StdinUnavailable: false}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil), NextProtos:[]string{"h2", "http/1.1"}}, UserAgent:"gobot", DisableCompression:false, Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), WarningHandler:rest.fakeWarningHandler{}, Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p), Proxy:(func(*http.Request) (*url.URL, error))(%p)}`,
   635  		c.Transport, fakeWrapperFunc, c.RateLimiter, fakeDialFunc, fakeProxyFunc,
   636  	)
   637  
   638  	for _, f := range []string{"%s", "%v", "%+v", "%#v"} {
   639  		if got := fmt.Sprintf(f, c); want != got {
   640  			t.Errorf("fmt.Sprintf(%q, c)\ngot:  %q\nwant: %q", f, got, want)
   641  		}
   642  	}
   643  }