k8s.io/client-go@v0.22.2/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  			Config:  Config{},
   167  			Limiter: flowcontrol.NewTokenBucketRateLimiter(5, 10),
   168  		},
   169  		{
   170  			Config:  Config{QPS: 10},
   171  			Limiter: flowcontrol.NewTokenBucketRateLimiter(10, 10),
   172  		},
   173  		{
   174  			Config:  Config{QPS: -1},
   175  			Limiter: nil,
   176  		},
   177  		{
   178  			Config: Config{
   179  				RateLimiter: flowcontrol.NewTokenBucketRateLimiter(11, 12),
   180  			},
   181  			Limiter: flowcontrol.NewTokenBucketRateLimiter(11, 12),
   182  		},
   183  	}
   184  	for _, testCase := range testCases {
   185  		t.Run("Versioned_"+testCase.Name, func(t *testing.T) {
   186  			config := testCase.Config
   187  			config.Host = "127.0.0.1"
   188  			config.ContentConfig = ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs}
   189  			client, err := RESTClientFor(&config)
   190  			if err != nil {
   191  				t.Fatalf("unexpected error: %v", err)
   192  			}
   193  			if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) {
   194  				t.Fatalf("unexpected rate limiter: %#v", client.rateLimiter)
   195  			}
   196  		})
   197  		t.Run("Unversioned_"+testCase.Name, func(t *testing.T) {
   198  			config := testCase.Config
   199  			config.Host = "127.0.0.1"
   200  			config.ContentConfig = ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs}
   201  			client, err := UnversionedRESTClientFor(&config)
   202  			if err != nil {
   203  				t.Fatalf("unexpected error: %v", err)
   204  			}
   205  			if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) {
   206  				t.Fatalf("unexpected rate limiter: %#v", client.rateLimiter)
   207  			}
   208  		})
   209  	}
   210  }
   211  
   212  type fakeLimiter struct {
   213  	FakeSaturation float64
   214  	FakeQPS        float32
   215  }
   216  
   217  func (t *fakeLimiter) TryAccept() bool {
   218  	return true
   219  }
   220  
   221  func (t *fakeLimiter) Saturation() float64 {
   222  	return t.FakeSaturation
   223  }
   224  
   225  func (t *fakeLimiter) QPS() float32 {
   226  	return t.FakeQPS
   227  }
   228  
   229  func (t *fakeLimiter) Wait(ctx context.Context) error {
   230  	return nil
   231  }
   232  
   233  func (t *fakeLimiter) Stop() {}
   234  
   235  func (t *fakeLimiter) Accept() {}
   236  
   237  type fakeCodec struct{}
   238  
   239  func (c *fakeCodec) Decode([]byte, *schema.GroupVersionKind, runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
   240  	return nil, nil, nil
   241  }
   242  
   243  func (c *fakeCodec) Encode(obj runtime.Object, stream io.Writer) error {
   244  	return nil
   245  }
   246  
   247  func (c *fakeCodec) Identifier() runtime.Identifier {
   248  	return runtime.Identifier("fake")
   249  }
   250  
   251  type fakeRoundTripper struct{}
   252  
   253  func (r *fakeRoundTripper) RoundTrip(*http.Request) (*http.Response, error) {
   254  	return nil, nil
   255  }
   256  
   257  var fakeWrapperFunc = func(http.RoundTripper) http.RoundTripper {
   258  	return &fakeRoundTripper{}
   259  }
   260  
   261  type fakeWarningHandler struct{}
   262  
   263  func (f fakeWarningHandler) HandleWarningHeader(code int, agent string, message string) {}
   264  
   265  type fakeNegotiatedSerializer struct{}
   266  
   267  func (n *fakeNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
   268  	return nil
   269  }
   270  
   271  func (n *fakeNegotiatedSerializer) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
   272  	return &fakeCodec{}
   273  }
   274  
   275  func (n *fakeNegotiatedSerializer) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
   276  	return &fakeCodec{}
   277  }
   278  
   279  var fakeDialFunc = func(ctx context.Context, network, addr string) (net.Conn, error) {
   280  	return nil, fakeDialerError
   281  }
   282  
   283  var fakeDialerError = errors.New("fakedialer")
   284  
   285  func fakeProxyFunc(*http.Request) (*url.URL, error) {
   286  	return nil, errors.New("fakeproxy")
   287  }
   288  
   289  type fakeAuthProviderConfigPersister struct{}
   290  
   291  func (fakeAuthProviderConfigPersister) Persist(map[string]string) error {
   292  	return fakeAuthProviderConfigPersisterError
   293  }
   294  
   295  var fakeAuthProviderConfigPersisterError = errors.New("fakeAuthProviderConfigPersisterError")
   296  
   297  func TestAnonymousConfig(t *testing.T) {
   298  	f := fuzz.New().NilChance(0.0).NumElements(1, 1)
   299  	f.Funcs(
   300  		func(r *runtime.Codec, f fuzz.Continue) {
   301  			codec := &fakeCodec{}
   302  			f.Fuzz(codec)
   303  			*r = codec
   304  		},
   305  		func(r *http.RoundTripper, f fuzz.Continue) {
   306  			roundTripper := &fakeRoundTripper{}
   307  			f.Fuzz(roundTripper)
   308  			*r = roundTripper
   309  		},
   310  		func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) {
   311  			*fn = fakeWrapperFunc
   312  		},
   313  		func(fn *transport.WrapperFunc, f fuzz.Continue) {
   314  			*fn = fakeWrapperFunc
   315  		},
   316  		func(r *runtime.NegotiatedSerializer, f fuzz.Continue) {
   317  			serializer := &fakeNegotiatedSerializer{}
   318  			f.Fuzz(serializer)
   319  			*r = serializer
   320  		},
   321  		func(r *flowcontrol.RateLimiter, f fuzz.Continue) {
   322  			limiter := &fakeLimiter{}
   323  			f.Fuzz(limiter)
   324  			*r = limiter
   325  		},
   326  		func(h *WarningHandler, f fuzz.Continue) {
   327  			*h = &fakeWarningHandler{}
   328  		},
   329  		// Authentication does not require fuzzer
   330  		func(r *AuthProviderConfigPersister, f fuzz.Continue) {},
   331  		func(r *clientcmdapi.AuthProviderConfig, f fuzz.Continue) {
   332  			r.Config = map[string]string{}
   333  		},
   334  		func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) {
   335  			*r = fakeDialFunc
   336  		},
   337  		func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
   338  			*r = fakeProxyFunc
   339  		},
   340  		func(r *runtime.Object, f fuzz.Continue) {
   341  			unknown := &runtime.Unknown{}
   342  			f.Fuzz(unknown)
   343  			*r = unknown
   344  		},
   345  	)
   346  	for i := 0; i < 20; i++ {
   347  		original := &Config{}
   348  		f.Fuzz(original)
   349  		actual := AnonymousClientConfig(original)
   350  		expected := *original
   351  
   352  		// this is the list of known security related fields, add to this list if a new field
   353  		// is added to Config, update AnonymousClientConfig to preserve the field otherwise.
   354  		expected.Impersonate = ImpersonationConfig{}
   355  		expected.BearerToken = ""
   356  		expected.BearerTokenFile = ""
   357  		expected.Username = ""
   358  		expected.Password = ""
   359  		expected.AuthProvider = nil
   360  		expected.AuthConfigPersister = nil
   361  		expected.ExecProvider = nil
   362  		expected.TLSClientConfig.CertData = nil
   363  		expected.TLSClientConfig.CertFile = ""
   364  		expected.TLSClientConfig.KeyData = nil
   365  		expected.TLSClientConfig.KeyFile = ""
   366  		expected.Transport = nil
   367  		expected.WrapTransport = nil
   368  
   369  		if actual.Dial != nil {
   370  			_, actualError := actual.Dial(context.Background(), "", "")
   371  			_, expectedError := expected.Dial(context.Background(), "", "")
   372  			if !reflect.DeepEqual(expectedError, actualError) {
   373  				t.Fatalf("AnonymousClientConfig dropped the Dial field")
   374  			}
   375  		}
   376  		actual.Dial = nil
   377  		expected.Dial = nil
   378  
   379  		if actual.Proxy != nil {
   380  			_, actualError := actual.Proxy(nil)
   381  			_, expectedError := expected.Proxy(nil)
   382  			if !reflect.DeepEqual(expectedError, actualError) {
   383  				t.Fatalf("AnonymousClientConfig dropped the Proxy field")
   384  			}
   385  		}
   386  		actual.Proxy = nil
   387  		expected.Proxy = nil
   388  
   389  		if diff := cmp.Diff(*actual, expected); diff != "" {
   390  			t.Fatalf("AnonymousClientConfig dropped unexpected fields, identify whether they are security related or not (-got, +want): %s", diff)
   391  		}
   392  	}
   393  }
   394  
   395  func TestCopyConfig(t *testing.T) {
   396  	f := fuzz.New().NilChance(0.0).NumElements(1, 1)
   397  	f.Funcs(
   398  		func(r *runtime.Codec, f fuzz.Continue) {
   399  			codec := &fakeCodec{}
   400  			f.Fuzz(codec)
   401  			*r = codec
   402  		},
   403  		func(r *http.RoundTripper, f fuzz.Continue) {
   404  			roundTripper := &fakeRoundTripper{}
   405  			f.Fuzz(roundTripper)
   406  			*r = roundTripper
   407  		},
   408  		func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) {
   409  			*fn = fakeWrapperFunc
   410  		},
   411  		func(fn *transport.WrapperFunc, f fuzz.Continue) {
   412  			*fn = fakeWrapperFunc
   413  		},
   414  		func(r *runtime.NegotiatedSerializer, f fuzz.Continue) {
   415  			serializer := &fakeNegotiatedSerializer{}
   416  			f.Fuzz(serializer)
   417  			*r = serializer
   418  		},
   419  		func(r *flowcontrol.RateLimiter, f fuzz.Continue) {
   420  			limiter := &fakeLimiter{}
   421  			f.Fuzz(limiter)
   422  			*r = limiter
   423  		},
   424  		func(h *WarningHandler, f fuzz.Continue) {
   425  			*h = &fakeWarningHandler{}
   426  		},
   427  		func(r *AuthProviderConfigPersister, f fuzz.Continue) {
   428  			*r = fakeAuthProviderConfigPersister{}
   429  		},
   430  		func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) {
   431  			*r = fakeDialFunc
   432  		},
   433  		func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
   434  			*r = fakeProxyFunc
   435  		},
   436  		func(r *runtime.Object, f fuzz.Continue) {
   437  			unknown := &runtime.Unknown{}
   438  			f.Fuzz(unknown)
   439  			*r = unknown
   440  		},
   441  	)
   442  	for i := 0; i < 20; i++ {
   443  		original := &Config{}
   444  		f.Fuzz(original)
   445  		actual := CopyConfig(original)
   446  		expected := *original
   447  
   448  		// this is the list of known risky fields, add to this list if a new field
   449  		// is added to Config, update CopyConfig to preserve the field otherwise.
   450  
   451  		// The DeepEqual cannot handle the func comparison, so we just verify if the
   452  		// function return the expected object.
   453  		if actual.WrapTransport == nil || !reflect.DeepEqual(expected.WrapTransport(nil), &fakeRoundTripper{}) {
   454  			t.Fatalf("CopyConfig dropped the WrapTransport field")
   455  		}
   456  		actual.WrapTransport = nil
   457  		expected.WrapTransport = nil
   458  
   459  		if actual.Dial != nil {
   460  			_, actualError := actual.Dial(context.Background(), "", "")
   461  			_, expectedError := expected.Dial(context.Background(), "", "")
   462  			if !reflect.DeepEqual(expectedError, actualError) {
   463  				t.Fatalf("CopyConfig  dropped the Dial field")
   464  			}
   465  		}
   466  		actual.Dial = nil
   467  		expected.Dial = nil
   468  
   469  		if actual.AuthConfigPersister != nil {
   470  			actualError := actual.AuthConfigPersister.Persist(nil)
   471  			expectedError := expected.AuthConfigPersister.Persist(nil)
   472  			if !reflect.DeepEqual(expectedError, actualError) {
   473  				t.Fatalf("CopyConfig  dropped the Dial field")
   474  			}
   475  		}
   476  		actual.AuthConfigPersister = nil
   477  		expected.AuthConfigPersister = nil
   478  
   479  		if actual.Proxy != nil {
   480  			_, actualError := actual.Proxy(nil)
   481  			_, expectedError := expected.Proxy(nil)
   482  			if !reflect.DeepEqual(expectedError, actualError) {
   483  				t.Fatalf("CopyConfig  dropped the Proxy field")
   484  			}
   485  		}
   486  		actual.Proxy = nil
   487  		expected.Proxy = nil
   488  
   489  		if diff := cmp.Diff(*actual, expected); diff != "" {
   490  			t.Fatalf("CopyConfig  dropped unexpected fields, identify whether they are security related or not (-got, +want): %s", diff)
   491  		}
   492  	}
   493  }
   494  
   495  func TestConfigStringer(t *testing.T) {
   496  	formatBytes := func(b []byte) string {
   497  		// %#v for []byte always pre-pends "[]byte{".
   498  		// %#v for struct with []byte field always pre-pends "[]uint8{".
   499  		return strings.Replace(fmt.Sprintf("%#v", b), "byte", "uint8", 1)
   500  	}
   501  	tests := []struct {
   502  		desc            string
   503  		c               *Config
   504  		expectContent   []string
   505  		prohibitContent []string
   506  	}{
   507  		{
   508  			desc:          "nil config",
   509  			c:             nil,
   510  			expectContent: []string{"<nil>"},
   511  		},
   512  		{
   513  			desc: "non-sensitive config",
   514  			c: &Config{
   515  				Host:      "localhost:8080",
   516  				APIPath:   "v1",
   517  				UserAgent: "gobot",
   518  			},
   519  			expectContent: []string{"localhost:8080", "v1", "gobot"},
   520  		},
   521  		{
   522  			desc: "sensitive config",
   523  			c: &Config{
   524  				Host:        "localhost:8080",
   525  				Username:    "gopher",
   526  				Password:    "g0ph3r",
   527  				BearerToken: "1234567890",
   528  				TLSClientConfig: TLSClientConfig{
   529  					CertFile: "a.crt",
   530  					KeyFile:  "a.key",
   531  					CertData: []byte("fake cert"),
   532  					KeyData:  []byte("fake key"),
   533  				},
   534  				AuthProvider: &clientcmdapi.AuthProviderConfig{
   535  					Config: map[string]string{"secret": "s3cr3t"},
   536  				},
   537  				ExecProvider: &clientcmdapi.ExecConfig{
   538  					Args:   []string{"secret"},
   539  					Env:    []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
   540  					Config: &runtime.Unknown{Raw: []byte("here is some config data")},
   541  				},
   542  			},
   543  			expectContent: []string{
   544  				"localhost:8080",
   545  				"gopher",
   546  				"a.crt",
   547  				"a.key",
   548  				"--- REDACTED ---",
   549  				formatBytes([]byte("--- REDACTED ---")),
   550  				formatBytes([]byte("--- TRUNCATED ---")),
   551  			},
   552  			prohibitContent: []string{
   553  				"g0ph3r",
   554  				"1234567890",
   555  				formatBytes([]byte("fake cert")),
   556  				formatBytes([]byte("fake key")),
   557  				"secret",
   558  				"s3cr3t",
   559  				"here is some config data",
   560  				formatBytes([]byte("super secret password")),
   561  			},
   562  		},
   563  	}
   564  
   565  	for _, tt := range tests {
   566  		t.Run(tt.desc, func(t *testing.T) {
   567  			got := tt.c.String()
   568  			t.Logf("formatted config: %q", got)
   569  
   570  			for _, expect := range tt.expectContent {
   571  				if !strings.Contains(got, expect) {
   572  					t.Errorf("missing expected string %q", expect)
   573  				}
   574  			}
   575  			for _, prohibit := range tt.prohibitContent {
   576  				if strings.Contains(got, prohibit) {
   577  					t.Errorf("found prohibited string %q", prohibit)
   578  				}
   579  			}
   580  		})
   581  	}
   582  }
   583  
   584  func TestConfigSprint(t *testing.T) {
   585  	c := &Config{
   586  		Host:    "localhost:8080",
   587  		APIPath: "v1",
   588  		ContentConfig: ContentConfig{
   589  			AcceptContentTypes: "application/json",
   590  			ContentType:        "application/json",
   591  		},
   592  		Username:    "gopher",
   593  		Password:    "g0ph3r",
   594  		BearerToken: "1234567890",
   595  		Impersonate: ImpersonationConfig{
   596  			UserName: "gopher2",
   597  		},
   598  		AuthProvider: &clientcmdapi.AuthProviderConfig{
   599  			Name:   "gopher",
   600  			Config: map[string]string{"secret": "s3cr3t"},
   601  		},
   602  		AuthConfigPersister: fakeAuthProviderConfigPersister{},
   603  		ExecProvider: &clientcmdapi.ExecConfig{
   604  			Command:            "sudo",
   605  			Args:               []string{"secret"},
   606  			Env:                []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
   607  			ProvideClusterInfo: true,
   608  			Config:             &runtime.Unknown{Raw: []byte("super secret password")},
   609  		},
   610  		TLSClientConfig: TLSClientConfig{
   611  			CertFile:   "a.crt",
   612  			KeyFile:    "a.key",
   613  			CertData:   []byte("fake cert"),
   614  			KeyData:    []byte("fake key"),
   615  			NextProtos: []string{"h2", "http/1.1"},
   616  		},
   617  		UserAgent:      "gobot",
   618  		Transport:      &fakeRoundTripper{},
   619  		WrapTransport:  fakeWrapperFunc,
   620  		QPS:            1,
   621  		Burst:          2,
   622  		RateLimiter:    &fakeLimiter{},
   623  		WarningHandler: fakeWarningHandler{},
   624  		Timeout:        3 * time.Second,
   625  		Dial:           fakeDialFunc,
   626  		Proxy:          fakeProxyFunc,
   627  	}
   628  	want := fmt.Sprintf(
   629  		`&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", 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)}`,
   630  		c.Transport, fakeWrapperFunc, c.RateLimiter, fakeDialFunc, fakeProxyFunc,
   631  	)
   632  
   633  	for _, f := range []string{"%s", "%v", "%+v", "%#v"} {
   634  		if got := fmt.Sprintf(f, c); want != got {
   635  			t.Errorf("fmt.Sprintf(%q, c)\ngot:  %q\nwant: %q", f, got, want)
   636  		}
   637  	}
   638  }