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