k8s.io/client-go@v0.22.2/tools/clientcmd/loader_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 "bytes" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "path" 25 "path/filepath" 26 "reflect" 27 "strings" 28 "testing" 29 30 "sigs.k8s.io/yaml" 31 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/apimachinery/pkg/util/diff" 34 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 35 clientcmdlatest "k8s.io/client-go/tools/clientcmd/api/latest" 36 ) 37 38 var ( 39 testConfigAlfa = clientcmdapi.Config{ 40 AuthInfos: map[string]*clientcmdapi.AuthInfo{ 41 "red-user": {Token: "red-token"}}, 42 Clusters: map[string]*clientcmdapi.Cluster{ 43 "cow-cluster": {Server: "http://cow.org:8080"}}, 44 Contexts: map[string]*clientcmdapi.Context{ 45 "federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster", Namespace: "hammer-ns"}}, 46 } 47 testConfigBravo = clientcmdapi.Config{ 48 AuthInfos: map[string]*clientcmdapi.AuthInfo{ 49 "black-user": {Token: "black-token"}}, 50 Clusters: map[string]*clientcmdapi.Cluster{ 51 "pig-cluster": {Server: "http://pig.org:8080"}}, 52 Contexts: map[string]*clientcmdapi.Context{ 53 "queen-anne-context": {AuthInfo: "black-user", Cluster: "pig-cluster", Namespace: "saw-ns"}}, 54 } 55 testConfigCharlie = clientcmdapi.Config{ 56 AuthInfos: map[string]*clientcmdapi.AuthInfo{ 57 "green-user": {Token: "green-token"}}, 58 Clusters: map[string]*clientcmdapi.Cluster{ 59 "horse-cluster": {Server: "http://horse.org:8080"}}, 60 Contexts: map[string]*clientcmdapi.Context{ 61 "shaker-context": {AuthInfo: "green-user", Cluster: "horse-cluster", Namespace: "chisel-ns"}}, 62 } 63 testConfigDelta = clientcmdapi.Config{ 64 AuthInfos: map[string]*clientcmdapi.AuthInfo{ 65 "blue-user": {Token: "blue-token"}}, 66 Clusters: map[string]*clientcmdapi.Cluster{ 67 "chicken-cluster": {Server: "http://chicken.org:8080"}}, 68 Contexts: map[string]*clientcmdapi.Context{ 69 "gothic-context": {AuthInfo: "blue-user", Cluster: "chicken-cluster", Namespace: "plane-ns"}}, 70 } 71 72 testConfigConflictAlfa = clientcmdapi.Config{ 73 AuthInfos: map[string]*clientcmdapi.AuthInfo{ 74 "red-user": {Token: "a-different-red-token"}, 75 "yellow-user": {Token: "yellow-token"}}, 76 Clusters: map[string]*clientcmdapi.Cluster{ 77 "cow-cluster": {Server: "http://a-different-cow.org:8080", InsecureSkipTLSVerify: true}, 78 "donkey-cluster": {Server: "http://donkey.org:8080", InsecureSkipTLSVerify: true}}, 79 CurrentContext: "federal-context", 80 } 81 ) 82 83 func TestNilOutMap(t *testing.T) { 84 var fakeKubeconfigData = `apiVersion: v1 85 kind: Config 86 clusters: 87 - cluster: 88 certificate-authority-data: UEhPTlkK 89 server: https://1.1.1.1 90 name: production 91 contexts: 92 - context: 93 cluster: production 94 user: production 95 name: production 96 current-context: production 97 users: 98 - name: production 99 user: 100 auth-provider: 101 name: gcp` 102 103 _, _, err := clientcmdlatest.Codec.Decode([]byte(fakeKubeconfigData), nil, nil) 104 if err != nil { 105 t.Fatalf("unexpected error: %v", err) 106 } 107 } 108 109 func TestNonExistentCommandLineFile(t *testing.T) { 110 loadingRules := ClientConfigLoadingRules{ 111 ExplicitPath: "bogus_file", 112 } 113 114 _, err := loadingRules.Load() 115 if err == nil { 116 t.Fatalf("Expected error for missing command-line file, got none") 117 } 118 if !strings.Contains(err.Error(), "bogus_file") { 119 t.Fatalf("Expected error about 'bogus_file', got %s", err.Error()) 120 } 121 } 122 123 func TestToleratingMissingFiles(t *testing.T) { 124 loadingRules := ClientConfigLoadingRules{ 125 Precedence: []string{"bogus1", "bogus2", "bogus3"}, 126 } 127 128 _, err := loadingRules.Load() 129 if err != nil { 130 t.Fatalf("Unexpected error: %v", err) 131 } 132 } 133 134 func TestErrorReadingFile(t *testing.T) { 135 commandLineFile, _ := ioutil.TempFile("", "") 136 defer os.Remove(commandLineFile.Name()) 137 138 if err := ioutil.WriteFile(commandLineFile.Name(), []byte("bogus value"), 0644); err != nil { 139 t.Fatalf("Error creating tempfile: %v", err) 140 } 141 142 loadingRules := ClientConfigLoadingRules{ 143 ExplicitPath: commandLineFile.Name(), 144 } 145 146 _, err := loadingRules.Load() 147 if err == nil { 148 t.Fatalf("Expected error for unloadable file, got none") 149 } 150 if !strings.Contains(err.Error(), commandLineFile.Name()) { 151 t.Fatalf("Expected error about '%s', got %s", commandLineFile.Name(), err.Error()) 152 } 153 } 154 155 func TestErrorReadingNonFile(t *testing.T) { 156 tmpdir, err := ioutil.TempDir("", "") 157 if err != nil { 158 t.Fatalf("Couldn't create tmpdir") 159 } 160 defer os.RemoveAll(tmpdir) 161 162 loadingRules := ClientConfigLoadingRules{ 163 ExplicitPath: tmpdir, 164 } 165 166 _, err = loadingRules.Load() 167 if err == nil { 168 t.Fatalf("Expected error for non-file, got none") 169 } 170 if !strings.Contains(err.Error(), tmpdir) { 171 t.Fatalf("Expected error about '%s', got %s", tmpdir, err.Error()) 172 } 173 } 174 175 func TestConflictingCurrentContext(t *testing.T) { 176 commandLineFile, _ := ioutil.TempFile("", "") 177 defer os.Remove(commandLineFile.Name()) 178 envVarFile, _ := ioutil.TempFile("", "") 179 defer os.Remove(envVarFile.Name()) 180 181 mockCommandLineConfig := clientcmdapi.Config{ 182 CurrentContext: "any-context-value", 183 } 184 mockEnvVarConfig := clientcmdapi.Config{ 185 CurrentContext: "a-different-context", 186 } 187 188 WriteToFile(mockCommandLineConfig, commandLineFile.Name()) 189 WriteToFile(mockEnvVarConfig, envVarFile.Name()) 190 191 loadingRules := ClientConfigLoadingRules{ 192 ExplicitPath: commandLineFile.Name(), 193 Precedence: []string{envVarFile.Name()}, 194 } 195 196 mergedConfig, err := loadingRules.Load() 197 if err != nil { 198 t.Errorf("Unexpected error: %v", err) 199 } 200 201 if mergedConfig.CurrentContext != mockCommandLineConfig.CurrentContext { 202 t.Errorf("expected %v, got %v", mockCommandLineConfig.CurrentContext, mergedConfig.CurrentContext) 203 } 204 } 205 206 func TestEncodeYAML(t *testing.T) { 207 config := clientcmdapi.Config{ 208 CurrentContext: "any-context-value", 209 Contexts: map[string]*clientcmdapi.Context{ 210 "433e40": { 211 Cluster: "433e40", 212 }, 213 }, 214 Clusters: map[string]*clientcmdapi.Cluster{ 215 "0": { 216 Server: "https://localhost:1234", 217 }, 218 "1": { 219 Server: "https://localhost:1234", 220 }, 221 "433e40": { 222 Server: "https://localhost:1234", 223 }, 224 }, 225 } 226 data, err := Write(config) 227 if err != nil { 228 t.Fatal(err) 229 } 230 expected := []byte(`apiVersion: v1 231 clusters: 232 - cluster: 233 server: https://localhost:1234 234 name: "0" 235 - cluster: 236 server: https://localhost:1234 237 name: "1" 238 - cluster: 239 server: https://localhost:1234 240 name: "433e40" 241 contexts: 242 - context: 243 cluster: "433e40" 244 user: "" 245 name: "433e40" 246 current-context: any-context-value 247 kind: Config 248 preferences: {} 249 users: null 250 `) 251 if !bytes.Equal(expected, data) { 252 t.Error(diff.ObjectReflectDiff(string(expected), string(data))) 253 } 254 } 255 256 func TestLoadingEmptyMaps(t *testing.T) { 257 configFile, _ := ioutil.TempFile("", "") 258 defer os.Remove(configFile.Name()) 259 260 mockConfig := clientcmdapi.Config{ 261 CurrentContext: "any-context-value", 262 } 263 264 WriteToFile(mockConfig, configFile.Name()) 265 266 config, err := LoadFromFile(configFile.Name()) 267 if err != nil { 268 t.Errorf("Unexpected error: %v", err) 269 } 270 271 if config.Clusters == nil { 272 t.Error("expected config.Clusters to be non-nil") 273 } 274 if config.AuthInfos == nil { 275 t.Error("expected config.AuthInfos to be non-nil") 276 } 277 if config.Contexts == nil { 278 t.Error("expected config.Contexts to be non-nil") 279 } 280 } 281 282 func TestDuplicateClusterName(t *testing.T) { 283 configFile, _ := ioutil.TempFile("", "") 284 defer os.Remove(configFile.Name()) 285 286 err := ioutil.WriteFile(configFile.Name(), []byte(` 287 kind: Config 288 apiVersion: v1 289 clusters: 290 - cluster: 291 api-version: v1 292 server: https://kubernetes.default.svc:443 293 certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 294 name: kubeconfig-cluster 295 - cluster: 296 api-version: v2 297 server: https://test.example.server:443 298 certificate-authority: /var/run/secrets/test.example.io/serviceaccount/ca.crt 299 name: kubeconfig-cluster 300 contexts: 301 - context: 302 cluster: kubeconfig-cluster 303 namespace: default 304 user: kubeconfig-user 305 name: kubeconfig-context 306 current-context: kubeconfig-context 307 users: 308 - name: kubeconfig-user 309 user: 310 tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 311 `), os.FileMode(0755)) 312 313 if err != nil { 314 t.Errorf("Unexpected error: %v", err) 315 } 316 317 _, err = LoadFromFile(configFile.Name()) 318 if err == nil || !strings.Contains(err.Error(), 319 "error converting *[]NamedCluster into *map[string]*api.Cluster: duplicate name \"kubeconfig-cluster\" in list") { 320 t.Error("Expected error in loading duplicate cluster name, got none") 321 } 322 } 323 324 func TestDuplicateContextName(t *testing.T) { 325 configFile, _ := ioutil.TempFile("", "") 326 defer os.Remove(configFile.Name()) 327 328 err := ioutil.WriteFile(configFile.Name(), []byte(` 329 kind: Config 330 apiVersion: v1 331 clusters: 332 - cluster: 333 api-version: v1 334 server: https://kubernetes.default.svc:443 335 certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 336 name: kubeconfig-cluster 337 contexts: 338 - context: 339 cluster: kubeconfig-cluster 340 namespace: default 341 user: kubeconfig-user 342 name: kubeconfig-context 343 - context: 344 cluster: test-example-cluster 345 namespace: test-example 346 user: test-example-user 347 name: kubeconfig-context 348 current-context: kubeconfig-context 349 users: 350 - name: kubeconfig-user 351 user: 352 tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 353 `), os.FileMode(0755)) 354 355 if err != nil { 356 t.Errorf("Unexpected error: %v", err) 357 } 358 359 _, err = LoadFromFile(configFile.Name()) 360 if err == nil || !strings.Contains(err.Error(), 361 "error converting *[]NamedContext into *map[string]*api.Context: duplicate name \"kubeconfig-context\" in list") { 362 t.Error("Expected error in loading duplicate context name, got none") 363 } 364 } 365 366 func TestDuplicateUserName(t *testing.T) { 367 configFile, _ := ioutil.TempFile("", "") 368 defer os.Remove(configFile.Name()) 369 370 err := ioutil.WriteFile(configFile.Name(), []byte(` 371 kind: Config 372 apiVersion: v1 373 clusters: 374 - cluster: 375 api-version: v1 376 server: https://kubernetes.default.svc:443 377 certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 378 name: kubeconfig-cluster 379 contexts: 380 - context: 381 cluster: kubeconfig-cluster 382 namespace: default 383 user: kubeconfig-user 384 name: kubeconfig-context 385 current-context: kubeconfig-context 386 users: 387 - name: kubeconfig-user 388 user: 389 tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 390 - name: kubeconfig-user 391 user: 392 tokenFile: /var/run/secrets/test.example.com/serviceaccount/token 393 `), os.FileMode(0755)) 394 395 if err != nil { 396 t.Errorf("Unexpected error: %v", err) 397 } 398 399 _, err = LoadFromFile(configFile.Name()) 400 if err == nil || !strings.Contains(err.Error(), 401 "error converting *[]NamedAuthInfo into *map[string]*api.AuthInfo: duplicate name \"kubeconfig-user\" in list") { 402 t.Error("Expected error in loading duplicate user name, got none") 403 } 404 } 405 406 func TestDuplicateExtensionName(t *testing.T) { 407 configFile, _ := ioutil.TempFile("", "") 408 defer os.Remove(configFile.Name()) 409 410 err := ioutil.WriteFile(configFile.Name(), []byte(` 411 kind: Config 412 apiVersion: v1 413 clusters: 414 - cluster: 415 api-version: v1 416 server: https://kubernetes.default.svc:443 417 certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 418 name: kubeconfig-cluster 419 contexts: 420 - context: 421 cluster: kubeconfig-cluster 422 namespace: default 423 user: kubeconfig-user 424 name: kubeconfig-context 425 current-context: kubeconfig-context 426 users: 427 - name: kubeconfig-user 428 user: 429 tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 430 extensions: 431 - extension: 432 bytes: test 433 name: test-extension 434 - extension: 435 bytes: some-example 436 name: test-extension 437 `), os.FileMode(0755)) 438 439 if err != nil { 440 t.Errorf("Unexpected error: %v", err) 441 } 442 443 _, err = LoadFromFile(configFile.Name()) 444 if err == nil || !strings.Contains(err.Error(), 445 "error converting *[]NamedExtension into *map[string]runtime.Object: duplicate name \"test-extension\" in list") { 446 t.Error("Expected error in loading duplicate extension name, got none") 447 } 448 } 449 450 func TestResolveRelativePaths(t *testing.T) { 451 pathResolutionConfig1 := clientcmdapi.Config{ 452 AuthInfos: map[string]*clientcmdapi.AuthInfo{ 453 "relative-user-1": {ClientCertificate: "relative/client/cert", ClientKey: "../relative/client/key"}, 454 "absolute-user-1": {ClientCertificate: "/absolute/client/cert", ClientKey: "/absolute/client/key"}, 455 "relative-cmd-1": {Exec: &clientcmdapi.ExecConfig{Command: "../relative/client/cmd"}}, 456 "absolute-cmd-1": {Exec: &clientcmdapi.ExecConfig{Command: "/absolute/client/cmd"}}, 457 "PATH-cmd-1": {Exec: &clientcmdapi.ExecConfig{Command: "cmd"}}, 458 }, 459 Clusters: map[string]*clientcmdapi.Cluster{ 460 "relative-server-1": {CertificateAuthority: "../relative/ca"}, 461 "absolute-server-1": {CertificateAuthority: "/absolute/ca"}, 462 }, 463 } 464 pathResolutionConfig2 := clientcmdapi.Config{ 465 AuthInfos: map[string]*clientcmdapi.AuthInfo{ 466 "relative-user-2": {ClientCertificate: "relative/client/cert2", ClientKey: "../relative/client/key2"}, 467 "absolute-user-2": {ClientCertificate: "/absolute/client/cert2", ClientKey: "/absolute/client/key2"}, 468 }, 469 Clusters: map[string]*clientcmdapi.Cluster{ 470 "relative-server-2": {CertificateAuthority: "../relative/ca2"}, 471 "absolute-server-2": {CertificateAuthority: "/absolute/ca2"}, 472 }, 473 } 474 475 configDir1, _ := ioutil.TempDir("", "") 476 defer os.RemoveAll(configDir1) 477 configFile1 := path.Join(configDir1, ".kubeconfig") 478 configDir1, _ = filepath.Abs(configDir1) 479 480 configDir2, _ := ioutil.TempDir("", "") 481 defer os.RemoveAll(configDir2) 482 configDir2, _ = ioutil.TempDir(configDir2, "") 483 configFile2 := path.Join(configDir2, ".kubeconfig") 484 configDir2, _ = filepath.Abs(configDir2) 485 486 WriteToFile(pathResolutionConfig1, configFile1) 487 WriteToFile(pathResolutionConfig2, configFile2) 488 489 loadingRules := ClientConfigLoadingRules{ 490 Precedence: []string{configFile1, configFile2}, 491 } 492 493 mergedConfig, err := loadingRules.Load() 494 if err != nil { 495 t.Errorf("Unexpected error: %v", err) 496 } 497 498 foundClusterCount := 0 499 for key, cluster := range mergedConfig.Clusters { 500 if key == "relative-server-1" { 501 foundClusterCount++ 502 matchStringArg(path.Join(configDir1, pathResolutionConfig1.Clusters["relative-server-1"].CertificateAuthority), cluster.CertificateAuthority, t) 503 } 504 if key == "relative-server-2" { 505 foundClusterCount++ 506 matchStringArg(path.Join(configDir2, pathResolutionConfig2.Clusters["relative-server-2"].CertificateAuthority), cluster.CertificateAuthority, t) 507 } 508 if key == "absolute-server-1" { 509 foundClusterCount++ 510 matchStringArg(pathResolutionConfig1.Clusters["absolute-server-1"].CertificateAuthority, cluster.CertificateAuthority, t) 511 } 512 if key == "absolute-server-2" { 513 foundClusterCount++ 514 matchStringArg(pathResolutionConfig2.Clusters["absolute-server-2"].CertificateAuthority, cluster.CertificateAuthority, t) 515 } 516 } 517 if foundClusterCount != 4 { 518 t.Errorf("Expected 4 clusters, found %v: %v", foundClusterCount, mergedConfig.Clusters) 519 } 520 521 foundAuthInfoCount := 0 522 for key, authInfo := range mergedConfig.AuthInfos { 523 if key == "relative-user-1" { 524 foundAuthInfoCount++ 525 matchStringArg(path.Join(configDir1, pathResolutionConfig1.AuthInfos["relative-user-1"].ClientCertificate), authInfo.ClientCertificate, t) 526 matchStringArg(path.Join(configDir1, pathResolutionConfig1.AuthInfos["relative-user-1"].ClientKey), authInfo.ClientKey, t) 527 } 528 if key == "relative-user-2" { 529 foundAuthInfoCount++ 530 matchStringArg(path.Join(configDir2, pathResolutionConfig2.AuthInfos["relative-user-2"].ClientCertificate), authInfo.ClientCertificate, t) 531 matchStringArg(path.Join(configDir2, pathResolutionConfig2.AuthInfos["relative-user-2"].ClientKey), authInfo.ClientKey, t) 532 } 533 if key == "absolute-user-1" { 534 foundAuthInfoCount++ 535 matchStringArg(pathResolutionConfig1.AuthInfos["absolute-user-1"].ClientCertificate, authInfo.ClientCertificate, t) 536 matchStringArg(pathResolutionConfig1.AuthInfos["absolute-user-1"].ClientKey, authInfo.ClientKey, t) 537 } 538 if key == "absolute-user-2" { 539 foundAuthInfoCount++ 540 matchStringArg(pathResolutionConfig2.AuthInfos["absolute-user-2"].ClientCertificate, authInfo.ClientCertificate, t) 541 matchStringArg(pathResolutionConfig2.AuthInfos["absolute-user-2"].ClientKey, authInfo.ClientKey, t) 542 } 543 if key == "relative-cmd-1" { 544 foundAuthInfoCount++ 545 matchStringArg(path.Join(configDir1, pathResolutionConfig1.AuthInfos[key].Exec.Command), authInfo.Exec.Command, t) 546 } 547 if key == "absolute-cmd-1" { 548 foundAuthInfoCount++ 549 matchStringArg(pathResolutionConfig1.AuthInfos[key].Exec.Command, authInfo.Exec.Command, t) 550 } 551 if key == "PATH-cmd-1" { 552 foundAuthInfoCount++ 553 matchStringArg(pathResolutionConfig1.AuthInfos[key].Exec.Command, authInfo.Exec.Command, t) 554 } 555 } 556 if foundAuthInfoCount != 7 { 557 t.Errorf("Expected 7 users, found %v: %v", foundAuthInfoCount, mergedConfig.AuthInfos) 558 } 559 560 } 561 562 func TestMigratingFile(t *testing.T) { 563 sourceFile, _ := ioutil.TempFile("", "") 564 defer os.Remove(sourceFile.Name()) 565 destinationFile, _ := ioutil.TempFile("", "") 566 // delete the file so that we'll write to it 567 os.Remove(destinationFile.Name()) 568 569 WriteToFile(testConfigAlfa, sourceFile.Name()) 570 571 loadingRules := ClientConfigLoadingRules{ 572 MigrationRules: map[string]string{destinationFile.Name(): sourceFile.Name()}, 573 } 574 575 if _, err := loadingRules.Load(); err != nil { 576 t.Errorf("unexpected error %v", err) 577 } 578 579 // the load should have recreated this file 580 defer os.Remove(destinationFile.Name()) 581 582 sourceContent, err := ioutil.ReadFile(sourceFile.Name()) 583 if err != nil { 584 t.Errorf("unexpected error %v", err) 585 } 586 destinationContent, err := ioutil.ReadFile(destinationFile.Name()) 587 if err != nil { 588 t.Errorf("unexpected error %v", err) 589 } 590 591 if !reflect.DeepEqual(sourceContent, destinationContent) { 592 t.Errorf("source and destination do not match") 593 } 594 } 595 596 func TestMigratingFileLeaveExistingFileAlone(t *testing.T) { 597 sourceFile, _ := ioutil.TempFile("", "") 598 defer os.Remove(sourceFile.Name()) 599 destinationFile, _ := ioutil.TempFile("", "") 600 defer os.Remove(destinationFile.Name()) 601 602 WriteToFile(testConfigAlfa, sourceFile.Name()) 603 604 loadingRules := ClientConfigLoadingRules{ 605 MigrationRules: map[string]string{destinationFile.Name(): sourceFile.Name()}, 606 } 607 608 if _, err := loadingRules.Load(); err != nil { 609 t.Errorf("unexpected error %v", err) 610 } 611 612 destinationContent, err := ioutil.ReadFile(destinationFile.Name()) 613 if err != nil { 614 t.Errorf("unexpected error %v", err) 615 } 616 617 if len(destinationContent) > 0 { 618 t.Errorf("destination should not have been touched") 619 } 620 } 621 622 func TestMigratingFileSourceMissingSkip(t *testing.T) { 623 sourceFilename := "some-missing-file" 624 destinationFile, _ := ioutil.TempFile("", "") 625 // delete the file so that we'll write to it 626 os.Remove(destinationFile.Name()) 627 628 loadingRules := ClientConfigLoadingRules{ 629 MigrationRules: map[string]string{destinationFile.Name(): sourceFilename}, 630 } 631 632 if _, err := loadingRules.Load(); err != nil { 633 t.Errorf("unexpected error %v", err) 634 } 635 636 if _, err := os.Stat(destinationFile.Name()); !os.IsNotExist(err) { 637 t.Errorf("destination should not exist") 638 } 639 } 640 641 func TestFileLocking(t *testing.T) { 642 f, _ := ioutil.TempFile("", "") 643 defer os.Remove(f.Name()) 644 645 err := lockFile(f.Name()) 646 if err != nil { 647 t.Errorf("unexpected error while locking file: %v", err) 648 } 649 defer unlockFile(f.Name()) 650 651 err = lockFile(f.Name()) 652 if err == nil { 653 t.Error("expected error while locking file.") 654 } 655 } 656 657 func Example_noMergingOnExplicitPaths() { 658 commandLineFile, _ := ioutil.TempFile("", "") 659 defer os.Remove(commandLineFile.Name()) 660 envVarFile, _ := ioutil.TempFile("", "") 661 defer os.Remove(envVarFile.Name()) 662 663 WriteToFile(testConfigAlfa, commandLineFile.Name()) 664 WriteToFile(testConfigConflictAlfa, envVarFile.Name()) 665 666 loadingRules := ClientConfigLoadingRules{ 667 ExplicitPath: commandLineFile.Name(), 668 Precedence: []string{envVarFile.Name()}, 669 } 670 671 mergedConfig, err := loadingRules.Load() 672 673 json, err := runtime.Encode(clientcmdlatest.Codec, mergedConfig) 674 if err != nil { 675 fmt.Printf("Unexpected error: %v", err) 676 } 677 output, err := yaml.JSONToYAML(json) 678 if err != nil { 679 fmt.Printf("Unexpected error: %v", err) 680 } 681 682 fmt.Printf("%v", string(output)) 683 // Output: 684 // apiVersion: v1 685 // clusters: 686 // - cluster: 687 // server: http://cow.org:8080 688 // name: cow-cluster 689 // contexts: 690 // - context: 691 // cluster: cow-cluster 692 // namespace: hammer-ns 693 // user: red-user 694 // name: federal-context 695 // current-context: "" 696 // kind: Config 697 // preferences: {} 698 // users: 699 // - name: red-user 700 // user: 701 // token: red-token 702 } 703 704 func Example_mergingSomeWithConflict() { 705 commandLineFile, _ := ioutil.TempFile("", "") 706 defer os.Remove(commandLineFile.Name()) 707 envVarFile, _ := ioutil.TempFile("", "") 708 defer os.Remove(envVarFile.Name()) 709 710 WriteToFile(testConfigAlfa, commandLineFile.Name()) 711 WriteToFile(testConfigConflictAlfa, envVarFile.Name()) 712 713 loadingRules := ClientConfigLoadingRules{ 714 Precedence: []string{commandLineFile.Name(), envVarFile.Name()}, 715 } 716 717 mergedConfig, err := loadingRules.Load() 718 719 json, err := runtime.Encode(clientcmdlatest.Codec, mergedConfig) 720 if err != nil { 721 fmt.Printf("Unexpected error: %v", err) 722 } 723 output, err := yaml.JSONToYAML(json) 724 if err != nil { 725 fmt.Printf("Unexpected error: %v", err) 726 } 727 728 fmt.Printf("%v", string(output)) 729 // Output: 730 // apiVersion: v1 731 // clusters: 732 // - cluster: 733 // server: http://cow.org:8080 734 // name: cow-cluster 735 // - cluster: 736 // insecure-skip-tls-verify: true 737 // server: http://donkey.org:8080 738 // name: donkey-cluster 739 // contexts: 740 // - context: 741 // cluster: cow-cluster 742 // namespace: hammer-ns 743 // user: red-user 744 // name: federal-context 745 // current-context: federal-context 746 // kind: Config 747 // preferences: {} 748 // users: 749 // - name: red-user 750 // user: 751 // token: red-token 752 // - name: yellow-user 753 // user: 754 // token: yellow-token 755 } 756 757 func Example_mergingEverythingNoConflicts() { 758 commandLineFile, _ := ioutil.TempFile("", "") 759 defer os.Remove(commandLineFile.Name()) 760 envVarFile, _ := ioutil.TempFile("", "") 761 defer os.Remove(envVarFile.Name()) 762 currentDirFile, _ := ioutil.TempFile("", "") 763 defer os.Remove(currentDirFile.Name()) 764 homeDirFile, _ := ioutil.TempFile("", "") 765 defer os.Remove(homeDirFile.Name()) 766 767 WriteToFile(testConfigAlfa, commandLineFile.Name()) 768 WriteToFile(testConfigBravo, envVarFile.Name()) 769 WriteToFile(testConfigCharlie, currentDirFile.Name()) 770 WriteToFile(testConfigDelta, homeDirFile.Name()) 771 772 loadingRules := ClientConfigLoadingRules{ 773 Precedence: []string{commandLineFile.Name(), envVarFile.Name(), currentDirFile.Name(), homeDirFile.Name()}, 774 } 775 776 mergedConfig, err := loadingRules.Load() 777 778 json, err := runtime.Encode(clientcmdlatest.Codec, mergedConfig) 779 if err != nil { 780 fmt.Printf("Unexpected error: %v", err) 781 } 782 output, err := yaml.JSONToYAML(json) 783 if err != nil { 784 fmt.Printf("Unexpected error: %v", err) 785 } 786 787 fmt.Printf("%v", string(output)) 788 // Output: 789 // apiVersion: v1 790 // clusters: 791 // - cluster: 792 // server: http://chicken.org:8080 793 // name: chicken-cluster 794 // - cluster: 795 // server: http://cow.org:8080 796 // name: cow-cluster 797 // - cluster: 798 // server: http://horse.org:8080 799 // name: horse-cluster 800 // - cluster: 801 // server: http://pig.org:8080 802 // name: pig-cluster 803 // contexts: 804 // - context: 805 // cluster: cow-cluster 806 // namespace: hammer-ns 807 // user: red-user 808 // name: federal-context 809 // - context: 810 // cluster: chicken-cluster 811 // namespace: plane-ns 812 // user: blue-user 813 // name: gothic-context 814 // - context: 815 // cluster: pig-cluster 816 // namespace: saw-ns 817 // user: black-user 818 // name: queen-anne-context 819 // - context: 820 // cluster: horse-cluster 821 // namespace: chisel-ns 822 // user: green-user 823 // name: shaker-context 824 // current-context: "" 825 // kind: Config 826 // preferences: {} 827 // users: 828 // - name: black-user 829 // user: 830 // token: black-token 831 // - name: blue-user 832 // user: 833 // token: blue-token 834 // - name: green-user 835 // user: 836 // token: green-token 837 // - name: red-user 838 // user: 839 // token: red-token 840 } 841 842 func TestDeduplicate(t *testing.T) { 843 testCases := []struct { 844 src []string 845 expect []string 846 }{ 847 { 848 src: []string{"a", "b", "c", "d", "e", "f"}, 849 expect: []string{"a", "b", "c", "d", "e", "f"}, 850 }, 851 { 852 src: []string{"a", "b", "c", "b", "e", "f"}, 853 expect: []string{"a", "b", "c", "e", "f"}, 854 }, 855 { 856 src: []string{"a", "a", "b", "b", "c", "b"}, 857 expect: []string{"a", "b", "c"}, 858 }, 859 } 860 861 for _, testCase := range testCases { 862 get := deduplicate(testCase.src) 863 if !reflect.DeepEqual(get, testCase.expect) { 864 t.Errorf("expect: %v, get: %v", testCase.expect, get) 865 } 866 } 867 } 868 869 func TestLoadingGetLoadingPrecedence(t *testing.T) { 870 testCases := map[string]struct { 871 rules *ClientConfigLoadingRules 872 env string 873 precedence []string 874 }{ 875 "default": { 876 precedence: []string{filepath.Join(os.Getenv("HOME"), ".kube/config")}, 877 }, 878 "explicit": { 879 rules: &ClientConfigLoadingRules{ 880 ExplicitPath: "/explicit/kubeconfig", 881 }, 882 precedence: []string{"/explicit/kubeconfig"}, 883 }, 884 "envvar-single": { 885 env: "/env/kubeconfig", 886 precedence: []string{"/env/kubeconfig"}, 887 }, 888 "envvar-multiple": { 889 env: "/env/kubeconfig:/other/kubeconfig", 890 precedence: []string{"/env/kubeconfig", "/other/kubeconfig"}, 891 }, 892 } 893 894 kubeconfig := os.Getenv("KUBECONFIG") 895 defer os.Setenv("KUBECONFIG", kubeconfig) 896 897 for name, test := range testCases { 898 t.Run(name, func(t *testing.T) { 899 os.Setenv("KUBECONFIG", test.env) 900 rules := test.rules 901 if rules == nil { 902 rules = NewDefaultClientConfigLoadingRules() 903 } 904 actual := rules.GetLoadingPrecedence() 905 if !reflect.DeepEqual(actual, test.precedence) { 906 t.Errorf("expect %v, got %v", test.precedence, actual) 907 } 908 }) 909 } 910 }