k8s.io/client-go@v0.31.1/tools/clientcmd/client_config_test.go (about) 1 /* 2 Copyright 2014 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 clientcmd 18 19 import ( 20 "os" 21 "reflect" 22 "strings" 23 "testing" 24 25 utiltesting "k8s.io/client-go/util/testing" 26 27 "github.com/imdario/mergo" 28 29 "k8s.io/apimachinery/pkg/runtime" 30 restclient "k8s.io/client-go/rest" 31 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 32 ) 33 34 func TestMergoSemantics(t *testing.T) { 35 type U struct { 36 A string 37 B int64 38 } 39 type T struct { 40 S []string 41 X string 42 Y int64 43 U U 44 } 45 var testDataStruct = []struct { 46 dst T 47 src T 48 expected T 49 }{ 50 { 51 dst: T{X: "one"}, 52 src: T{X: "two"}, 53 expected: T{X: "two"}, 54 }, 55 { 56 dst: T{X: "one", Y: 5, U: U{A: "four", B: 6}}, 57 src: T{X: "two", U: U{A: "three", B: 4}}, 58 expected: T{X: "two", Y: 5, U: U{A: "three", B: 4}}, 59 }, 60 { 61 dst: T{S: []string{"test3", "test4", "test5"}}, 62 src: T{S: []string{"test1", "test2", "test3"}}, 63 expected: T{S: []string{"test1", "test2", "test3"}}, 64 }, 65 } 66 for _, data := range testDataStruct { 67 err := mergo.Merge(&data.dst, &data.src, mergo.WithOverride) 68 if err != nil { 69 t.Errorf("error while merging: %s", err) 70 } 71 if !reflect.DeepEqual(data.dst, data.expected) { 72 // The mergo library has previously changed in a an incompatible way. 73 // example: 74 // 75 // https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a 76 // 77 // This test verifies that the semantics of the merge are what we expect. 78 // If they are not, the mergo library may have been updated and broken 79 // unexpectedly. 80 t.Errorf("mergo.MergeWithOverwrite did not provide expected output: %+v doesn't match %+v", data.dst, data.expected) 81 } 82 } 83 84 var testDataMap = []struct { 85 dst map[string]int 86 src map[string]int 87 expected map[string]int 88 }{ 89 { 90 dst: map[string]int{"rsc": 6543, "r": 2138, "gri": 1908, "adg": 912, "prt": 22}, 91 src: map[string]int{"rsc": 3711, "r": 2138, "gri": 1908, "adg": 912}, 92 expected: map[string]int{"rsc": 3711, "r": 2138, "gri": 1908, "adg": 912, "prt": 22}, 93 }, 94 } 95 for _, data := range testDataMap { 96 err := mergo.Merge(&data.dst, &data.src, mergo.WithOverride) 97 if err != nil { 98 t.Errorf("error while merging: %s", err) 99 } 100 if !reflect.DeepEqual(data.dst, data.expected) { 101 // The mergo library has previously changed in a an incompatible way. 102 // example: 103 // 104 // https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a 105 // 106 // This test verifies that the semantics of the merge are what we expect. 107 // If they are not, the mergo library may have been updated and broken 108 // unexpectedly. 109 t.Errorf("mergo.MergeWithOverwrite did not provide expected output: %+v doesn't match %+v", data.dst, data.expected) 110 } 111 } 112 } 113 114 func createValidTestConfig() *clientcmdapi.Config { 115 const ( 116 server = "https://anything.com:8080" 117 token = "the-token" 118 ) 119 120 config := clientcmdapi.NewConfig() 121 config.Clusters["clean"] = &clientcmdapi.Cluster{ 122 Server: server, 123 } 124 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 125 Token: token, 126 } 127 config.Contexts["clean"] = &clientcmdapi.Context{ 128 Cluster: "clean", 129 AuthInfo: "clean", 130 } 131 config.CurrentContext = "clean" 132 133 return config 134 } 135 136 func createCAValidTestConfig() *clientcmdapi.Config { 137 138 config := createValidTestConfig() 139 config.Clusters["clean"].CertificateAuthorityData = []byte{0, 0} 140 return config 141 } 142 143 func TestDisableCompression(t *testing.T) { 144 config := createValidTestConfig() 145 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{ 146 ClusterInfo: clientcmdapi.Cluster{ 147 DisableCompression: true, 148 }, 149 }, nil) 150 151 actualCfg, err := clientBuilder.ClientConfig() 152 if err != nil { 153 t.Fatalf("Unexpected error: %v", err) 154 } 155 156 matchBoolArg(true, actualCfg.DisableCompression, t) 157 } 158 159 func TestInsecureOverridesCA(t *testing.T) { 160 config := createCAValidTestConfig() 161 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{ 162 ClusterInfo: clientcmdapi.Cluster{ 163 InsecureSkipTLSVerify: true, 164 }, 165 }, nil) 166 167 actualCfg, err := clientBuilder.ClientConfig() 168 if err != nil { 169 t.Fatalf("Unexpected error: %v", err) 170 } 171 172 matchBoolArg(true, actualCfg.Insecure, t) 173 matchStringArg("", actualCfg.TLSClientConfig.CAFile, t) 174 matchByteArg(nil, actualCfg.TLSClientConfig.CAData, t) 175 } 176 177 func TestCAOverridesCAData(t *testing.T) { 178 file, err := os.CreateTemp("", "my.ca") 179 if err != nil { 180 t.Fatalf("could not create tempfile: %v", err) 181 } 182 defer utiltesting.CloseAndRemove(t, file) 183 184 config := createCAValidTestConfig() 185 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{ 186 ClusterInfo: clientcmdapi.Cluster{ 187 CertificateAuthority: file.Name(), 188 }, 189 }, nil) 190 191 actualCfg, err := clientBuilder.ClientConfig() 192 if err != nil { 193 t.Fatalf("Unexpected error: %v", err) 194 } 195 196 matchBoolArg(false, actualCfg.Insecure, t) 197 matchStringArg(file.Name(), actualCfg.TLSClientConfig.CAFile, t) 198 matchByteArg(nil, actualCfg.TLSClientConfig.CAData, t) 199 } 200 201 func TestTLSServerName(t *testing.T) { 202 config := createValidTestConfig() 203 204 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{ 205 ClusterInfo: clientcmdapi.Cluster{ 206 TLSServerName: "overridden-server-name", 207 }, 208 }, nil) 209 210 actualCfg, err := clientBuilder.ClientConfig() 211 if err != nil { 212 t.Errorf("Unexpected error: %v", err) 213 } 214 215 matchStringArg("overridden-server-name", actualCfg.ServerName, t) 216 matchStringArg("", actualCfg.TLSClientConfig.CAFile, t) 217 matchByteArg(nil, actualCfg.TLSClientConfig.CAData, t) 218 } 219 220 func TestTLSServerNameClearsWhenServerNameSet(t *testing.T) { 221 config := createValidTestConfig() 222 223 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{ 224 ClusterInfo: clientcmdapi.Cluster{ 225 Server: "http://something", 226 }, 227 }, nil) 228 229 actualCfg, err := clientBuilder.ClientConfig() 230 if err != nil { 231 t.Errorf("Unexpected error: %v", err) 232 } 233 234 matchStringArg("", actualCfg.ServerName, t) 235 } 236 237 func TestFullImpersonateConfig(t *testing.T) { 238 config := createValidTestConfig() 239 config.Clusters["clean"] = &clientcmdapi.Cluster{ 240 Server: "https://localhost:8443", 241 } 242 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 243 Impersonate: "alice", 244 ImpersonateUID: "abc123", 245 ImpersonateGroups: []string{"group-1"}, 246 ImpersonateUserExtra: map[string][]string{"some-key": {"some-value"}}, 247 } 248 config.Contexts["clean"] = &clientcmdapi.Context{ 249 Cluster: "clean", 250 AuthInfo: "clean", 251 } 252 config.CurrentContext = "clean" 253 254 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{ 255 ClusterInfo: clientcmdapi.Cluster{ 256 Server: "http://something", 257 }, 258 }, nil) 259 260 actualCfg, err := clientBuilder.ClientConfig() 261 if err != nil { 262 t.Errorf("Unexpected error: %v", err) 263 } 264 265 matchStringArg("alice", actualCfg.Impersonate.UserName, t) 266 matchStringArg("abc123", actualCfg.Impersonate.UID, t) 267 matchIntArg(1, len(actualCfg.Impersonate.Groups), t) 268 matchStringArg("group-1", actualCfg.Impersonate.Groups[0], t) 269 matchIntArg(1, len(actualCfg.Impersonate.Extra), t) 270 matchIntArg(1, len(actualCfg.Impersonate.Extra["some-key"]), t) 271 matchStringArg("some-value", actualCfg.Impersonate.Extra["some-key"][0], t) 272 } 273 274 func TestMergeContext(t *testing.T) { 275 const namespace = "overridden-namespace" 276 277 config := createValidTestConfig() 278 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil) 279 280 _, overridden, err := clientBuilder.Namespace() 281 if err != nil { 282 t.Errorf("Unexpected error: %v", err) 283 } 284 285 if overridden { 286 t.Error("Expected namespace to not be overridden") 287 } 288 289 clientBuilder = NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{ 290 Context: clientcmdapi.Context{ 291 Namespace: namespace, 292 }, 293 }, nil) 294 295 actual, overridden, err := clientBuilder.Namespace() 296 if err != nil { 297 t.Errorf("Unexpected error: %v", err) 298 } 299 300 if !overridden { 301 t.Error("Expected namespace to be overridden") 302 } 303 304 matchStringArg(namespace, actual, t) 305 } 306 307 func TestModifyContext(t *testing.T) { 308 expectedCtx := map[string]bool{ 309 "updated": true, 310 "clean": true, 311 } 312 313 tempPath, err := os.CreateTemp("", "testclientcmd-") 314 if err != nil { 315 t.Fatalf("unexpected error: %v", err) 316 } 317 defer utiltesting.CloseAndRemove(t, tempPath) 318 pathOptions := NewDefaultPathOptions() 319 config := createValidTestConfig() 320 321 pathOptions.GlobalFile = tempPath.Name() 322 323 // define new context and assign it - our path options config 324 config.Contexts["updated"] = &clientcmdapi.Context{ 325 Cluster: "updated", 326 AuthInfo: "updated", 327 } 328 config.CurrentContext = "updated" 329 330 if err := ModifyConfig(pathOptions, *config, true); err != nil { 331 t.Errorf("Unexpected error: %v", err) 332 } 333 334 startingConfig, err := pathOptions.GetStartingConfig() 335 if err != nil { 336 t.Fatalf("Unexpected error: %v", err) 337 } 338 339 // make sure the current context was updated 340 matchStringArg("updated", startingConfig.CurrentContext, t) 341 342 // there should now be two contexts 343 if len(startingConfig.Contexts) != len(expectedCtx) { 344 t.Fatalf("unexpected number of contexts, expecting %v, but found %v", len(expectedCtx), len(startingConfig.Contexts)) 345 } 346 347 for key := range startingConfig.Contexts { 348 if !expectedCtx[key] { 349 t.Fatalf("expected context %q to exist", key) 350 } 351 } 352 } 353 354 func TestCertificateData(t *testing.T) { 355 caData := []byte("ca-data") 356 certData := []byte("cert-data") 357 keyData := []byte("key-data") 358 359 config := clientcmdapi.NewConfig() 360 config.Clusters["clean"] = &clientcmdapi.Cluster{ 361 Server: "https://localhost:8443", 362 CertificateAuthorityData: caData, 363 } 364 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 365 ClientCertificateData: certData, 366 ClientKeyData: keyData, 367 } 368 config.Contexts["clean"] = &clientcmdapi.Context{ 369 Cluster: "clean", 370 AuthInfo: "clean", 371 } 372 config.CurrentContext = "clean" 373 374 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil) 375 376 clientConfig, err := clientBuilder.ClientConfig() 377 if err != nil { 378 t.Fatalf("Unexpected error: %v", err) 379 } 380 381 // Make sure cert data gets into config (will override file paths) 382 matchByteArg(caData, clientConfig.TLSClientConfig.CAData, t) 383 matchByteArg(certData, clientConfig.TLSClientConfig.CertData, t) 384 matchByteArg(keyData, clientConfig.TLSClientConfig.KeyData, t) 385 } 386 387 func TestProxyURL(t *testing.T) { 388 tests := []struct { 389 desc string 390 proxyURL string 391 expectErr bool 392 }{ 393 { 394 desc: "no proxy-url", 395 }, 396 { 397 desc: "socks5 proxy-url", 398 proxyURL: "socks5://example.com", 399 }, 400 { 401 desc: "https proxy-url", 402 proxyURL: "https://example.com", 403 }, 404 { 405 desc: "http proxy-url", 406 proxyURL: "http://example.com", 407 }, 408 { 409 desc: "bad scheme proxy-url", 410 proxyURL: "socks6://example.com", 411 expectErr: true, 412 }, 413 { 414 desc: "no scheme proxy-url", 415 proxyURL: "example.com", 416 expectErr: true, 417 }, 418 { 419 desc: "not a url proxy-url", 420 proxyURL: "chewbacca@example.com", 421 expectErr: true, 422 }, 423 } 424 425 for _, test := range tests { 426 t.Run(test.proxyURL, func(t *testing.T) { 427 428 config := clientcmdapi.NewConfig() 429 config.Clusters["clean"] = &clientcmdapi.Cluster{ 430 Server: "https://localhost:8443", 431 ProxyURL: test.proxyURL, 432 } 433 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{} 434 config.Contexts["clean"] = &clientcmdapi.Context{ 435 Cluster: "clean", 436 AuthInfo: "clean", 437 } 438 config.CurrentContext = "clean" 439 440 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil) 441 442 clientConfig, err := clientBuilder.ClientConfig() 443 if test.expectErr { 444 if err == nil { 445 t.Fatal("Expected error constructing config") 446 } 447 return 448 } 449 if err != nil { 450 t.Fatalf("Unexpected error constructing config: %v", err) 451 } 452 453 if test.proxyURL == "" { 454 return 455 } 456 gotURL, err := clientConfig.Proxy(nil) 457 if err != nil { 458 t.Fatalf("Unexpected error from proxier: %v", err) 459 } 460 matchStringArg(test.proxyURL, gotURL.String(), t) 461 }) 462 } 463 } 464 465 func TestBasicAuthData(t *testing.T) { 466 username := "myuser" 467 password := "mypass" // Fake value for testing. 468 469 config := clientcmdapi.NewConfig() 470 config.Clusters["clean"] = &clientcmdapi.Cluster{ 471 Server: "https://localhost:8443", 472 } 473 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 474 Username: username, 475 Password: password, 476 } 477 config.Contexts["clean"] = &clientcmdapi.Context{ 478 Cluster: "clean", 479 AuthInfo: "clean", 480 } 481 config.CurrentContext = "clean" 482 483 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil) 484 485 clientConfig, err := clientBuilder.ClientConfig() 486 if err != nil { 487 t.Fatalf("Unexpected error: %v", err) 488 } 489 490 // Make sure basic auth data gets into config 491 matchStringArg(username, clientConfig.Username, t) 492 matchStringArg(password, clientConfig.Password, t) 493 } 494 495 func TestBasicTokenFile(t *testing.T) { 496 token := "exampletoken" 497 f, err := os.CreateTemp("", "tokenfile") 498 if err != nil { 499 t.Errorf("Unexpected error: %v", err) 500 return 501 } 502 defer utiltesting.CloseAndRemove(t, f) 503 if err := os.WriteFile(f.Name(), []byte(token), 0644); err != nil { 504 t.Errorf("Unexpected error: %v", err) 505 return 506 } 507 508 config := clientcmdapi.NewConfig() 509 config.Clusters["clean"] = &clientcmdapi.Cluster{ 510 Server: "https://localhost:8443", 511 } 512 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 513 TokenFile: f.Name(), 514 } 515 config.Contexts["clean"] = &clientcmdapi.Context{ 516 Cluster: "clean", 517 AuthInfo: "clean", 518 } 519 config.CurrentContext = "clean" 520 521 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil) 522 523 clientConfig, err := clientBuilder.ClientConfig() 524 if err != nil { 525 t.Fatalf("Unexpected error: %v", err) 526 } 527 528 matchStringArg(token, clientConfig.BearerToken, t) 529 } 530 531 func TestPrecedenceTokenFile(t *testing.T) { 532 token := "exampletoken" 533 f, err := os.CreateTemp("", "tokenfile") 534 if err != nil { 535 t.Errorf("Unexpected error: %v", err) 536 return 537 } 538 defer utiltesting.CloseAndRemove(t, f) 539 if err := os.WriteFile(f.Name(), []byte(token), 0644); err != nil { 540 t.Errorf("Unexpected error: %v", err) 541 return 542 } 543 544 config := clientcmdapi.NewConfig() 545 config.Clusters["clean"] = &clientcmdapi.Cluster{ 546 Server: "https://localhost:8443", 547 } 548 expectedToken := "expected" 549 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 550 Token: expectedToken, 551 TokenFile: f.Name(), 552 } 553 config.Contexts["clean"] = &clientcmdapi.Context{ 554 Cluster: "clean", 555 AuthInfo: "clean", 556 } 557 config.CurrentContext = "clean" 558 559 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil) 560 561 clientConfig, err := clientBuilder.ClientConfig() 562 if err != nil { 563 t.Fatalf("Unexpected error: %v", err) 564 } 565 566 matchStringArg(expectedToken, clientConfig.BearerToken, t) 567 } 568 569 func TestCreateClean(t *testing.T) { 570 config := createValidTestConfig() 571 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil) 572 573 clientConfig, err := clientBuilder.ClientConfig() 574 if err != nil { 575 t.Errorf("Unexpected error: %v", err) 576 } 577 578 matchStringArg(config.Clusters["clean"].Server, clientConfig.Host, t) 579 matchStringArg("", clientConfig.APIPath, t) 580 matchBoolArg(config.Clusters["clean"].InsecureSkipTLSVerify, clientConfig.Insecure, t) 581 matchStringArg(config.AuthInfos["clean"].Token, clientConfig.BearerToken, t) 582 matchStringArg(config.Clusters["clean"].TLSServerName, clientConfig.ServerName, t) 583 } 584 585 func TestCreateCleanWithPrefix(t *testing.T) { 586 tt := []struct { 587 server string 588 host string 589 }{ 590 {"https://anything.com:8080/foo/bar", "https://anything.com:8080/foo/bar"}, 591 {"http://anything.com:8080/foo/bar", "http://anything.com:8080/foo/bar"}, 592 {"http://anything.com:8080/foo/bar/", "http://anything.com:8080/foo/bar/"}, 593 {"http://anything.com:8080/", "http://anything.com:8080/"}, 594 {"http://anything.com:8080//", "http://anything.com:8080//"}, 595 {"anything.com:8080/foo/bar", "anything.com:8080/foo/bar"}, 596 {"anything.com:8080", "anything.com:8080"}, 597 {"anything.com", "anything.com"}, 598 {"anything", "anything"}, 599 } 600 601 tt = append(tt, struct{ server, host string }{"", "http://localhost:8080"}) 602 603 for _, tc := range tt { 604 config := createValidTestConfig() 605 606 cleanConfig := config.Clusters["clean"] 607 cleanConfig.Server = tc.server 608 config.Clusters["clean"] = cleanConfig 609 610 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{ 611 ClusterDefaults: clientcmdapi.Cluster{Server: "http://localhost:8080"}, 612 }, nil) 613 614 clientConfig, err := clientBuilder.ClientConfig() 615 if err != nil { 616 t.Fatalf("Unexpected error: %v", err) 617 } 618 619 matchStringArg(tc.host, clientConfig.Host, t) 620 } 621 } 622 623 func TestCreateCleanDefault(t *testing.T) { 624 config := createValidTestConfig() 625 clientBuilder := NewDefaultClientConfig(*config, &ConfigOverrides{}) 626 627 clientConfig, err := clientBuilder.ClientConfig() 628 if err != nil { 629 t.Fatalf("Unexpected error: %v", err) 630 } 631 632 matchStringArg(config.Clusters["clean"].Server, clientConfig.Host, t) 633 matchStringArg(config.Clusters["clean"].TLSServerName, clientConfig.ServerName, t) 634 matchBoolArg(config.Clusters["clean"].InsecureSkipTLSVerify, clientConfig.Insecure, t) 635 matchStringArg(config.AuthInfos["clean"].Token, clientConfig.BearerToken, t) 636 } 637 638 func TestCreateCleanDefaultCluster(t *testing.T) { 639 config := createValidTestConfig() 640 clientBuilder := NewDefaultClientConfig(*config, &ConfigOverrides{ 641 ClusterDefaults: clientcmdapi.Cluster{Server: "http://localhost:8080"}, 642 }) 643 644 clientConfig, err := clientBuilder.ClientConfig() 645 if err != nil { 646 t.Fatalf("Unexpected error: %v", err) 647 } 648 649 matchStringArg(config.Clusters["clean"].Server, clientConfig.Host, t) 650 matchStringArg(config.Clusters["clean"].TLSServerName, clientConfig.ServerName, t) 651 matchBoolArg(config.Clusters["clean"].InsecureSkipTLSVerify, clientConfig.Insecure, t) 652 matchStringArg(config.AuthInfos["clean"].Token, clientConfig.BearerToken, t) 653 } 654 655 func TestCreateMissingContextNoDefault(t *testing.T) { 656 config := createValidTestConfig() 657 clientBuilder := NewNonInteractiveClientConfig(*config, "not-present", &ConfigOverrides{}, nil) 658 659 _, err := clientBuilder.ClientConfig() 660 if err == nil { 661 t.Fatalf("Unexpected error: %v", err) 662 } 663 } 664 665 func TestCreateMissingContext(t *testing.T) { 666 const expectedErrorContains = "context was not found for specified context: not-present" 667 config := createValidTestConfig() 668 clientBuilder := NewNonInteractiveClientConfig(*config, "not-present", &ConfigOverrides{ 669 ClusterDefaults: clientcmdapi.Cluster{Server: "http://localhost:8080"}, 670 }, nil) 671 672 _, err := clientBuilder.ClientConfig() 673 if err == nil { 674 t.Fatalf("Expected error: %v", expectedErrorContains) 675 } 676 if !strings.Contains(err.Error(), expectedErrorContains) { 677 t.Fatalf("Expected error: %v, but got %v", expectedErrorContains, err) 678 } 679 } 680 681 func TestCreateAuthConfigExecInstallHintCleanup(t *testing.T) { 682 config := createValidTestConfig() 683 clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{ 684 AuthInfo: clientcmdapi.AuthInfo{ 685 Exec: &clientcmdapi.ExecConfig{ 686 APIVersion: "client.authentication.k8s.io/v1alpha1", 687 Command: "some-command", 688 InstallHint: "some install hint with \x1b[1mcontrol chars\x1b[0m\nand a newline", 689 InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, 690 }, 691 }, 692 }, nil) 693 cleanedInstallHint := "some install hint with U+001B[1mcontrol charsU+001B[0m\nand a newline" 694 695 clientConfig, err := clientBuilder.ClientConfig() 696 if err != nil { 697 t.Fatalf("Unexpected error: %v", err) 698 } 699 matchStringArg(cleanedInstallHint, clientConfig.ExecProvider.InstallHint, t) 700 } 701 702 func TestInClusterClientConfigPrecedence(t *testing.T) { 703 tt := []struct { 704 overrides *ConfigOverrides 705 }{ 706 { 707 overrides: &ConfigOverrides{ 708 ClusterInfo: clientcmdapi.Cluster{ 709 Server: "https://host-from-overrides.com", 710 }, 711 }, 712 }, 713 { 714 overrides: &ConfigOverrides{ 715 AuthInfo: clientcmdapi.AuthInfo{ 716 Token: "https://host-from-overrides.com", 717 }, 718 }, 719 }, 720 { 721 overrides: &ConfigOverrides{ 722 ClusterInfo: clientcmdapi.Cluster{ 723 CertificateAuthority: "/path/to/ca-from-overrides.crt", 724 }, 725 }, 726 }, 727 { 728 overrides: &ConfigOverrides{ 729 ClusterInfo: clientcmdapi.Cluster{ 730 Server: "https://host-from-overrides.com", 731 }, 732 AuthInfo: clientcmdapi.AuthInfo{ 733 Token: "https://host-from-overrides.com", 734 }, 735 }, 736 }, 737 { 738 overrides: &ConfigOverrides{ 739 ClusterInfo: clientcmdapi.Cluster{ 740 Server: "https://host-from-overrides.com", 741 CertificateAuthority: "/path/to/ca-from-overrides.crt", 742 }, 743 }, 744 }, 745 { 746 overrides: &ConfigOverrides{ 747 ClusterInfo: clientcmdapi.Cluster{ 748 CertificateAuthority: "/path/to/ca-from-overrides.crt", 749 }, 750 AuthInfo: clientcmdapi.AuthInfo{ 751 Token: "https://host-from-overrides.com", 752 }, 753 }, 754 }, 755 { 756 overrides: &ConfigOverrides{ 757 ClusterInfo: clientcmdapi.Cluster{ 758 Server: "https://host-from-overrides.com", 759 CertificateAuthority: "/path/to/ca-from-overrides.crt", 760 }, 761 AuthInfo: clientcmdapi.AuthInfo{ 762 Token: "https://host-from-overrides.com", 763 }, 764 }, 765 }, 766 { 767 overrides: &ConfigOverrides{ 768 ClusterInfo: clientcmdapi.Cluster{ 769 Server: "https://host-from-overrides.com", 770 CertificateAuthority: "/path/to/ca-from-overrides.crt", 771 }, 772 AuthInfo: clientcmdapi.AuthInfo{ 773 Token: "token-from-override", 774 TokenFile: "tokenfile-from-override", 775 }, 776 }, 777 }, 778 { 779 overrides: &ConfigOverrides{ 780 ClusterInfo: clientcmdapi.Cluster{ 781 Server: "https://host-from-overrides.com", 782 CertificateAuthority: "/path/to/ca-from-overrides.crt", 783 }, 784 AuthInfo: clientcmdapi.AuthInfo{ 785 Token: "", 786 TokenFile: "tokenfile-from-override", 787 }, 788 }, 789 }, 790 { 791 overrides: &ConfigOverrides{}, 792 }, 793 } 794 795 for _, tc := range tt { 796 expectedServer := "https://host-from-cluster.com" 797 expectedToken := "token-from-cluster" 798 expectedTokenFile := "tokenfile-from-cluster" 799 expectedCAFile := "/path/to/ca-from-cluster.crt" 800 801 icc := &inClusterClientConfig{ 802 inClusterConfigProvider: func() (*restclient.Config, error) { 803 return &restclient.Config{ 804 Host: expectedServer, 805 BearerToken: expectedToken, 806 BearerTokenFile: expectedTokenFile, 807 TLSClientConfig: restclient.TLSClientConfig{ 808 CAFile: expectedCAFile, 809 }, 810 }, nil 811 }, 812 overrides: tc.overrides, 813 } 814 815 clientConfig, err := icc.ClientConfig() 816 if err != nil { 817 t.Fatalf("Unxpected error: %v", err) 818 } 819 820 if overridenServer := tc.overrides.ClusterInfo.Server; len(overridenServer) > 0 { 821 expectedServer = overridenServer 822 } 823 if len(tc.overrides.AuthInfo.Token) > 0 || len(tc.overrides.AuthInfo.TokenFile) > 0 { 824 expectedToken = tc.overrides.AuthInfo.Token 825 expectedTokenFile = tc.overrides.AuthInfo.TokenFile 826 } 827 if overridenCAFile := tc.overrides.ClusterInfo.CertificateAuthority; len(overridenCAFile) > 0 { 828 expectedCAFile = overridenCAFile 829 } 830 831 if clientConfig.Host != expectedServer { 832 t.Errorf("Expected server %v, got %v", expectedServer, clientConfig.Host) 833 } 834 if clientConfig.BearerToken != expectedToken { 835 t.Errorf("Expected token %v, got %v", expectedToken, clientConfig.BearerToken) 836 } 837 if clientConfig.BearerTokenFile != expectedTokenFile { 838 t.Errorf("Expected tokenfile %v, got %v", expectedTokenFile, clientConfig.BearerTokenFile) 839 } 840 if clientConfig.TLSClientConfig.CAFile != expectedCAFile { 841 t.Errorf("Expected Certificate Authority %v, got %v", expectedCAFile, clientConfig.TLSClientConfig.CAFile) 842 } 843 } 844 } 845 846 func matchBoolArg(expected, got bool, t *testing.T) { 847 if expected != got { 848 t.Errorf("Expected %v, got %v", expected, got) 849 } 850 } 851 852 func matchStringArg(expected, got string, t *testing.T) { 853 if expected != got { 854 t.Errorf("Expected %q, got %q", expected, got) 855 } 856 } 857 858 func matchByteArg(expected, got []byte, t *testing.T) { 859 if !reflect.DeepEqual(expected, got) { 860 t.Errorf("Expected %v, got %v", expected, got) 861 } 862 } 863 864 func matchIntArg(expected, got int, t *testing.T) { 865 if expected != got { 866 t.Errorf("Expected %d, got %d", expected, got) 867 } 868 } 869 870 func TestNamespaceOverride(t *testing.T) { 871 config := &DirectClientConfig{ 872 overrides: &ConfigOverrides{ 873 Context: clientcmdapi.Context{ 874 Namespace: "foo", 875 }, 876 }, 877 } 878 879 ns, overridden, err := config.Namespace() 880 881 if err != nil { 882 t.Errorf("Unexpected error: %v", err) 883 } 884 885 if !overridden { 886 t.Errorf("Expected overridden = true") 887 } 888 889 matchStringArg("foo", ns, t) 890 } 891 892 func TestAuthConfigMerge(t *testing.T) { 893 content := ` 894 apiVersion: v1 895 clusters: 896 - cluster: 897 server: https://localhost:8080 898 extensions: 899 - name: client.authentication.k8s.io/exec 900 extension: 901 audience: foo 902 other: bar 903 name: foo-cluster 904 contexts: 905 - context: 906 cluster: foo-cluster 907 user: foo-user 908 namespace: bar 909 name: foo-context 910 current-context: foo-context 911 kind: Config 912 users: 913 - name: foo-user 914 user: 915 exec: 916 apiVersion: client.authentication.k8s.io/v1alpha1 917 args: 918 - arg-1 919 - arg-2 920 command: foo-command 921 provideClusterInfo: true 922 ` 923 tmpfile, err := os.CreateTemp("", "kubeconfig") 924 if err != nil { 925 t.Error(err) 926 } 927 defer utiltesting.CloseAndRemove(t, tmpfile) 928 if err := os.WriteFile(tmpfile.Name(), []byte(content), 0666); err != nil { 929 t.Error(err) 930 } 931 config, err := BuildConfigFromFlags("", tmpfile.Name()) 932 if err != nil { 933 t.Error(err) 934 } 935 if !reflect.DeepEqual(config.ExecProvider.Args, []string{"arg-1", "arg-2"}) { 936 t.Errorf("Got args %v when they should be %v\n", config.ExecProvider.Args, []string{"arg-1", "arg-2"}) 937 } 938 if !config.ExecProvider.ProvideClusterInfo { 939 t.Error("Wanted provider cluster info to be true") 940 } 941 want := &runtime.Unknown{ 942 Raw: []byte(`{"audience":"foo","other":"bar"}`), 943 ContentType: "application/json", 944 } 945 if !reflect.DeepEqual(config.ExecProvider.Config, want) { 946 t.Errorf("Got config %v when it should be %v\n", config.ExecProvider.Config, want) 947 } 948 } 949 950 func TestCleanANSIEscapeCodes(t *testing.T) { 951 tests := []struct { 952 name string 953 in, out string 954 }{ 955 { 956 name: "DenyBoldCharacters", 957 in: "\x1b[1mbold tuna\x1b[0m, fish, \x1b[1mbold marlin\x1b[0m", 958 out: "U+001B[1mbold tunaU+001B[0m, fish, U+001B[1mbold marlinU+001B[0m", 959 }, 960 { 961 name: "DenyCursorNavigation", 962 in: "\x1b[2Aup up, \x1b[2Cright right", 963 out: "U+001B[2Aup up, U+001B[2Cright right", 964 }, 965 { 966 name: "DenyClearScreen", 967 in: "clear: \x1b[2J", 968 out: "clear: U+001B[2J", 969 }, 970 { 971 name: "AllowSpaceCharactersUnchanged", 972 in: "tuna\nfish\r\nmarlin\t\r\ntuna\vfish\fmarlin", 973 }, 974 { 975 name: "AllowLetters", 976 in: "alpha: \u03b1, beta: \u03b2, gamma: \u03b3", 977 }, 978 { 979 name: "AllowMarks", 980 in: "tu\u0301na with a mark over the u, fi\u0302sh with a mark over the i," + 981 " ma\u030Arlin with a mark over the a", 982 }, 983 { 984 name: "AllowNumbers", 985 in: "t1na, f2sh, m3rlin, t12a, f34h, m56lin, t123, f456, m567n", 986 }, 987 { 988 name: "AllowPunctuation", 989 in: "\"here's a sentence; with! some...punctuation ;)\"", 990 }, 991 { 992 name: "AllowSymbols", 993 in: "the integral of f(x) from 0 to n approximately equals the sum of f(x)" + 994 " from a = 0 to n, where a and n are natural numbers:" + 995 "\u222b\u2081\u207F f(x) dx \u2248 \u2211\u2090\u208C\u2081\u207F f(x)," + 996 " a \u2208 \u2115, n \u2208 \u2115", 997 }, 998 { 999 name: "AllowSepatators", 1000 in: "here is a paragraph separator\u2029and here\u2003are\u2003some" + 1001 "\u2003em\u2003spaces", 1002 }, 1003 } 1004 for _, test := range tests { 1005 t.Run(test.name, func(t *testing.T) { 1006 if len(test.out) == 0 { 1007 test.out = test.in 1008 } 1009 1010 if actualOut := cleanANSIEscapeCodes(test.in); test.out != actualOut { 1011 t.Errorf("expected %q, actual %q", test.out, actualOut) 1012 } 1013 }) 1014 } 1015 } 1016 1017 func TestMergeRawConfigDoOverride(t *testing.T) { 1018 const ( 1019 server = "https://anything.com:8080" 1020 token = "the-token" 1021 modifiedServer = "http://localhost:8081" 1022 modifiedToken = "modified-token" 1023 ) 1024 config := createValidTestConfig() 1025 1026 // add another context which to modify with overrides 1027 config.Clusters["modify"] = &clientcmdapi.Cluster{ 1028 Server: server, 1029 } 1030 config.AuthInfos["modify"] = &clientcmdapi.AuthInfo{ 1031 Token: token, 1032 } 1033 config.Contexts["modify"] = &clientcmdapi.Context{ 1034 Cluster: "modify", 1035 AuthInfo: "modify", 1036 Namespace: "modify", 1037 } 1038 1039 // create overrides for the modify context 1040 overrides := &ConfigOverrides{ 1041 ClusterInfo: clientcmdapi.Cluster{ 1042 Server: modifiedServer, 1043 }, 1044 Context: clientcmdapi.Context{ 1045 Namespace: "foobar", 1046 Cluster: "modify", 1047 AuthInfo: "modify", 1048 }, 1049 AuthInfo: clientcmdapi.AuthInfo{ 1050 Token: modifiedToken, 1051 }, 1052 CurrentContext: "modify", 1053 } 1054 1055 cut := NewDefaultClientConfig(*config, overrides) 1056 act, err := cut.MergedRawConfig() 1057 if err != nil { 1058 t.Fatalf("Unexpected error: %v", err) 1059 } 1060 1061 // ensure overrides were applied to "modify" 1062 actContext := act.CurrentContext 1063 if actContext != "modify" { 1064 t.Errorf("Expected context %v, got %v", "modify", actContext) 1065 } 1066 if act.Clusters[actContext].Server != "http://localhost:8081" { 1067 t.Errorf("Expected server %v, got %v", "http://localhost:8081", act.Clusters[actContext].Server) 1068 } 1069 if act.Contexts[actContext].Namespace != "foobar" { 1070 t.Errorf("Expected namespace %v, got %v", "foobar", act.Contexts[actContext].Namespace) 1071 } 1072 1073 // ensure context "clean" was not touched 1074 if act.Clusters["clean"].Server != config.Clusters["clean"].Server { 1075 t.Errorf("Expected server %v, got %v", config.Clusters["clean"].Server, act.Clusters["clean"].Server) 1076 } 1077 if act.Contexts["clean"].Namespace != config.Contexts["clean"].Namespace { 1078 t.Errorf("Expected namespace %v, got %v", config.Contexts["clean"].Namespace, act.Contexts["clean"].Namespace) 1079 } 1080 }