istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/multicluster/remote_secret_test.go (about) 1 // Copyright Istio Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package multicluster 16 17 import ( 18 "bytes" 19 "errors" 20 "fmt" 21 "path/filepath" 22 "strings" 23 "testing" 24 "time" 25 26 "github.com/google/go-cmp/cmp" 27 . "github.com/onsi/gomega" 28 "github.com/spf13/pflag" 29 v1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/types" 33 "k8s.io/client-go/tools/clientcmd/api" 34 35 "istio.io/istio/istioctl/pkg/cli" 36 "istio.io/istio/operator/pkg/object" 37 "istio.io/istio/pkg/kube" 38 "istio.io/istio/pkg/kube/multicluster" 39 "istio.io/istio/pkg/test" 40 "istio.io/istio/pkg/test/env" 41 ) 42 43 var ( 44 kubeSystemNamespaceUID = types.UID("54643f96-eca0-11e9-bb97-42010a80000a") 45 kubeSystemNamespace = &v1.Namespace{ 46 ObjectMeta: metav1.ObjectMeta{ 47 Name: "kube-system", 48 UID: kubeSystemNamespaceUID, 49 }, 50 } 51 ) 52 53 const ( 54 testNamespace = "istio-system-test" 55 testServiceAccountName = "test-service-account" 56 ) 57 58 func makeServiceAccount(secrets ...string) *v1.ServiceAccount { 59 sa := &v1.ServiceAccount{ 60 ObjectMeta: metav1.ObjectMeta{ 61 Name: testServiceAccountName, 62 Namespace: testNamespace, 63 }, 64 } 65 66 for _, secret := range secrets { 67 sa.Secrets = append(sa.Secrets, v1.ObjectReference{ 68 Name: secret, 69 Namespace: testNamespace, 70 }) 71 } 72 73 return sa 74 } 75 76 func makeSecret(name, caData, token string) *v1.Secret { 77 out := &v1.Secret{ 78 ObjectMeta: metav1.ObjectMeta{ 79 Name: name, 80 Namespace: testNamespace, 81 Annotations: map[string]string{v1.ServiceAccountNameKey: testServiceAccountName}, 82 }, 83 Data: map[string][]byte{}, 84 Type: v1.SecretTypeServiceAccountToken, 85 } 86 if len(caData) > 0 { 87 out.Data[v1.ServiceAccountRootCAKey] = []byte(caData) 88 } 89 if len(token) > 0 { 90 out.Data[v1.ServiceAccountTokenKey] = []byte(token) 91 } 92 return out 93 } 94 95 type fakeOutputWriter struct { 96 b bytes.Buffer 97 injectError error 98 failAfter int 99 } 100 101 func (w *fakeOutputWriter) Write(p []byte) (n int, err error) { 102 w.failAfter-- 103 if w.failAfter <= 0 && w.injectError != nil { 104 return 0, w.injectError 105 } 106 return w.b.Write(p) 107 } 108 func (w *fakeOutputWriter) String() string { return w.b.String() } 109 110 func TestCreateRemoteSecrets(t *testing.T) { 111 prevOutputWriterStub := makeOutputWriterTestHook 112 defer func() { makeOutputWriterTestHook = prevOutputWriterStub }() 113 114 prevTokenWaitBackoff := tokenWaitBackoff 115 defer func() { tokenWaitBackoff = prevTokenWaitBackoff }() 116 117 sa := makeServiceAccount("saSecret") 118 sa2 := makeServiceAccount("saSecret", "saSecret2") 119 saSecret := makeSecret("saSecret", "caData", "token") 120 saSecret2 := makeSecret("saSecret2", "caData", "token") 121 saSecretMissingToken := makeSecret("saSecret", "caData", "") 122 123 tokenWaitBackoff = 10 * time.Millisecond 124 125 cases := []struct { 126 testName string 127 128 objs []runtime.Object 129 name string 130 secType SecretType 131 secretName string 132 133 // inject errors 134 badStartingConfig bool 135 outputWriterError error 136 137 want string 138 wantErrStr string 139 k8sMinorVersion string 140 }{ 141 { 142 testName: "fail to create remote secret token", 143 objs: []runtime.Object{kubeSystemNamespace, sa, saSecretMissingToken}, 144 wantErrStr: `no "token" data found`, 145 }, 146 { 147 testName: "fail to encode secret", 148 objs: []runtime.Object{kubeSystemNamespace, sa, saSecret}, 149 outputWriterError: errors.New("injected encode error"), 150 wantErrStr: "injected encode error", 151 }, 152 { 153 testName: "success", 154 objs: []runtime.Object{kubeSystemNamespace, sa, saSecret}, 155 name: "cluster-foo", 156 want: "cal-want", 157 }, 158 { 159 testName: "success with type defined", 160 objs: []runtime.Object{kubeSystemNamespace, sa, saSecret}, 161 name: "cluster-foo", 162 secType: "config", 163 want: "cal-want", 164 }, 165 { 166 testName: "failure due to multiple secrets", 167 objs: []runtime.Object{kubeSystemNamespace, sa2, saSecret, saSecret2}, 168 name: "cluster-foo", 169 wantErrStr: "wrong number of secrets (2) in serviceaccount", 170 // for k8s 1.24+ we auto-create a secret instead of relying on a reference in service account 171 k8sMinorVersion: "23", 172 }, 173 { 174 testName: "success when specific secret name provided", 175 objs: []runtime.Object{kubeSystemNamespace, sa2, saSecret, saSecret2}, 176 secretName: saSecret.Name, 177 name: "cluster-foo", 178 want: "cal-want", 179 }, 180 { 181 testName: "fail when non-existing secret name provided", 182 objs: []runtime.Object{kubeSystemNamespace, sa2, saSecret, saSecret2}, 183 secretName: "nonexistingSecret", 184 name: "cluster-foo", 185 want: "cal-want", 186 wantErrStr: "provided secret does not exist", 187 k8sMinorVersion: "23", 188 }, 189 } 190 191 for i := range cases { 192 c := &cases[i] 193 t.Run(fmt.Sprintf("[%v] %v", i, c.testName), func(tt *testing.T) { 194 makeOutputWriterTestHook = func() writer { 195 return &fakeOutputWriter{injectError: c.outputWriterError} 196 } 197 if c.secType != SecretTypeConfig { 198 c.secType = SecretTypeRemote 199 } 200 opts := RemoteSecretOptions{ 201 ServiceAccountName: testServiceAccountName, 202 AuthType: RemoteSecretAuthTypeBearerToken, 203 // ClusterName: testCluster, 204 KubeOptions: KubeOptions{ 205 Namespace: testNamespace, 206 }, 207 Type: c.secType, 208 SecretName: c.secretName, 209 } 210 211 ctx := cli.NewFakeContext(&cli.NewFakeContextOption{ 212 IstioNamespace: "istio-system", 213 Objects: c.objs, 214 Namespace: testNamespace, 215 Version: c.k8sMinorVersion, 216 }) 217 client, err := ctx.CLIClient() 218 if err != nil { 219 tt.Fatalf("failed to create client: %v", err) 220 } 221 got, _, err := CreateRemoteSecret(client, opts) 222 if c.wantErrStr != "" { 223 if err == nil { 224 tt.Fatalf("wanted error including %q but got none", c.wantErrStr) 225 } else if !strings.Contains(err.Error(), c.wantErrStr) { 226 tt.Fatalf("wanted error including %q but got %v", c.wantErrStr, err) 227 } 228 } else if c.wantErrStr == "" && err != nil { 229 tt.Fatalf("wanted non-error but got %q", err) 230 } else if c.want != "" { 231 var secretName, key string 232 switch c.secType { 233 case SecretTypeConfig: 234 secretName = configSecretName 235 key = configSecretKey 236 default: 237 secretName = remoteSecretPrefix + string(kubeSystemNamespaceUID) 238 key = "54643f96-eca0-11e9-bb97-42010a80000a" 239 } 240 wantOutput := fmt.Sprintf(`# This file is autogenerated, do not edit. 241 apiVersion: v1 242 kind: Secret 243 metadata: 244 annotations: 245 %s: 54643f96-eca0-11e9-bb97-42010a80000a 246 creationTimestamp: null 247 labels: 248 istio/multiCluster: "true" 249 name: %s 250 namespace: istio-system-test 251 stringData: 252 %s: | 253 apiVersion: v1 254 clusters: 255 - cluster: 256 certificate-authority-data: Y2FEYXRh 257 server: server 258 name: 54643f96-eca0-11e9-bb97-42010a80000a 259 contexts: 260 - context: 261 cluster: 54643f96-eca0-11e9-bb97-42010a80000a 262 user: 54643f96-eca0-11e9-bb97-42010a80000a 263 name: 54643f96-eca0-11e9-bb97-42010a80000a 264 current-context: 54643f96-eca0-11e9-bb97-42010a80000a 265 kind: Config 266 preferences: {} 267 users: 268 - name: 54643f96-eca0-11e9-bb97-42010a80000a 269 user: 270 token: token 271 --- 272 `, clusterNameAnnotationKey, secretName, key) 273 274 if diff := cmp.Diff(got, wantOutput); diff != "" { 275 tt.Errorf("got\n%v\nwant\n%vdiff %v", got, c.want, diff) 276 } 277 } 278 }) 279 } 280 } 281 282 func TestGetServiceAccountSecretToken(t *testing.T) { 283 secret := makeSecret("secret", "caData", "token") 284 285 type tc struct { 286 name string 287 opts RemoteSecretOptions 288 objs []runtime.Object 289 290 want *v1.Secret 291 wantErrStr string 292 } 293 294 commonCases := []tc{ 295 { 296 name: "missing service account", 297 opts: RemoteSecretOptions{ 298 ServiceAccountName: testServiceAccountName, 299 KubeOptions: KubeOptions{ 300 Namespace: testNamespace, 301 }, 302 ManifestsPath: filepath.Join(env.IstioSrc, "manifests"), 303 }, 304 wantErrStr: fmt.Sprintf("serviceaccounts %q not found", testServiceAccountName), 305 }, 306 } 307 308 legacyCases := append([]tc{ 309 { 310 name: "wrong number of secrets", 311 opts: RemoteSecretOptions{ 312 ServiceAccountName: testServiceAccountName, 313 CreateServiceAccount: false, 314 KubeOptions: KubeOptions{ 315 Namespace: testNamespace, 316 }, 317 ManifestsPath: filepath.Join(env.IstioSrc, "manifests"), 318 }, 319 objs: []runtime.Object{ 320 makeServiceAccount("secret", "extra-secret"), 321 }, 322 wantErrStr: "wrong number of secrets", 323 }, 324 { 325 name: "missing service account token secret", 326 opts: RemoteSecretOptions{ 327 ServiceAccountName: testServiceAccountName, 328 KubeOptions: KubeOptions{ 329 Namespace: testNamespace, 330 }, 331 ManifestsPath: filepath.Join(env.IstioSrc, "manifests"), 332 }, 333 objs: []runtime.Object{ 334 makeServiceAccount("wrong-secret"), 335 secret, 336 }, 337 wantErrStr: `secrets "wrong-secret" not found`, 338 }, 339 { 340 name: "success", 341 opts: RemoteSecretOptions{ 342 ServiceAccountName: testServiceAccountName, 343 KubeOptions: KubeOptions{ 344 Namespace: testNamespace, 345 }, 346 ManifestsPath: filepath.Join(env.IstioSrc, "manifests"), 347 }, 348 objs: []runtime.Object{ 349 makeServiceAccount("secret"), 350 secret, 351 }, 352 want: secret, 353 }, 354 }, commonCases...) 355 356 cases := append([]tc{ 357 { 358 name: "success", 359 opts: RemoteSecretOptions{ 360 ServiceAccountName: testServiceAccountName, 361 KubeOptions: KubeOptions{ 362 Namespace: testNamespace, 363 }, 364 ManifestsPath: filepath.Join(env.IstioSrc, "manifests"), 365 }, 366 objs: []runtime.Object{ 367 makeServiceAccount(tokenSecretName(testServiceAccountName)), 368 }, 369 want: &v1.Secret{ 370 ObjectMeta: metav1.ObjectMeta{ 371 Name: tokenSecretName(testServiceAccountName), 372 Namespace: testNamespace, 373 Annotations: map[string]string{v1.ServiceAccountNameKey: testServiceAccountName}, 374 }, 375 Type: v1.SecretTypeServiceAccountToken, 376 }, 377 }, 378 }, commonCases...) 379 380 doCase := func(t *testing.T, c tc, k8sMinorVer string) { 381 t.Run(fmt.Sprintf("%v", c.name), func(tt *testing.T) { 382 client := kube.NewFakeClientWithVersion(k8sMinorVer, c.objs...) 383 got, err := getServiceAccountSecret(client, c.opts) 384 if c.wantErrStr != "" { 385 if err == nil { 386 tt.Fatalf("wanted error including %q but got none", c.wantErrStr) 387 } else if !strings.Contains(err.Error(), c.wantErrStr) { 388 tt.Fatalf("wanted error including %q but got %v", c.wantErrStr, err) 389 } 390 } else if c.wantErrStr == "" && err != nil { 391 tt.Fatalf("wanted non-error but got %q", err) 392 } else if diff := cmp.Diff(got, c.want); diff != "" { 393 tt.Errorf("got\n%v\nwant\n%vdiff %v", got, c.want, diff) 394 } 395 }) 396 } 397 398 t.Run("kubernetes created secret (legacy)", func(t *testing.T) { 399 for _, c := range legacyCases { 400 doCase(t, c, "23") 401 } 402 }) 403 t.Run("istioctl created secret", func(t *testing.T) { 404 for _, c := range cases { 405 doCase(t, c, "") 406 } 407 }) 408 } 409 410 func TestGenerateServiceAccount(t *testing.T) { 411 opts := RemoteSecretOptions{ 412 CreateServiceAccount: true, 413 ManifestsPath: filepath.Join(env.IstioSrc, "manifests"), 414 KubeOptions: KubeOptions{ 415 Namespace: "istio-system", 416 }, 417 } 418 yaml, err := generateServiceAccountYAML(opts) 419 if err != nil { 420 t.Fatalf("failed to generate service account YAML: %v", err) 421 } 422 objs, err := object.ParseK8sObjectsFromYAMLManifest(yaml) 423 if err != nil { 424 t.Fatalf("could not parse k8s objects from generated YAML: %v", err) 425 } 426 427 mustFindObject(t, objs, "istio-reader-service-account", "ServiceAccount") 428 mustFindObject(t, objs, "istio-reader-clusterrole-istio-system", "ClusterRole") 429 mustFindObject(t, objs, "istio-reader-clusterrole-istio-system", "ClusterRoleBinding") 430 } 431 432 func mustFindObject(t test.Failer, objs object.K8sObjects, name, kind string) { 433 t.Helper() 434 var obj *object.K8sObject 435 for _, o := range objs { 436 if o.Kind == kind && o.Name == name { 437 obj = o 438 break 439 } 440 } 441 if obj == nil { 442 t.Fatalf("expected %v/%v", name, kind) 443 } 444 } 445 446 func TestCreateRemoteKubeconfig(t *testing.T) { 447 fakeClusterName := "fake-clusterName-0" 448 kubeconfig := strings.ReplaceAll(`apiVersion: v1 449 clusters: 450 - cluster: 451 certificate-authority-data: Y2FEYXRh 452 server: https://1.2.3.4 453 name: {cluster} 454 contexts: 455 - context: 456 cluster: {cluster} 457 user: {cluster} 458 name: {cluster} 459 current-context: {cluster} 460 kind: Config 461 preferences: {} 462 users: 463 - name: {cluster} 464 user: 465 token: token 466 `, "{cluster}", fakeClusterName) 467 468 cases := []struct { 469 name string 470 clusterName string 471 context string 472 server string 473 haveTokenSecret *v1.Secret 474 updatedTokenSecret *v1.Secret 475 wantRemoteSecret *v1.Secret 476 wantErrStr string 477 }{ 478 { 479 name: "missing caData", 480 haveTokenSecret: makeSecret("", "", "token"), 481 context: "c0", 482 clusterName: fakeClusterName, 483 wantErrStr: errMissingRootCAKey.Error(), 484 }, 485 { 486 name: "missing token", 487 haveTokenSecret: makeSecret("", "caData", ""), 488 context: "c0", 489 clusterName: fakeClusterName, 490 wantErrStr: errMissingTokenKey.Error(), 491 }, 492 { 493 name: "bad server name", 494 haveTokenSecret: makeSecret("", "caData", "token"), 495 context: "c0", 496 clusterName: fakeClusterName, 497 server: "", 498 wantErrStr: "invalid kubeconfig:", 499 }, 500 { 501 name: "success after wait", 502 haveTokenSecret: makeSecret("", "caData", ""), 503 updatedTokenSecret: makeSecret("", "caData", "token"), // token is populated later 504 context: "c0", 505 clusterName: fakeClusterName, 506 server: "https://1.2.3.4", 507 wantRemoteSecret: &v1.Secret{ 508 ObjectMeta: metav1.ObjectMeta{ 509 Name: remoteSecretNameFromClusterName(fakeClusterName), 510 Annotations: map[string]string{ 511 clusterNameAnnotationKey: fakeClusterName, 512 }, 513 Labels: map[string]string{ 514 multicluster.MultiClusterSecretLabel: "true", 515 }, 516 }, 517 Data: map[string][]byte{ 518 fakeClusterName: []byte(kubeconfig), 519 }, 520 }, 521 }, 522 { 523 name: "success", 524 haveTokenSecret: makeSecret("", "caData", "token"), 525 context: "c0", 526 clusterName: fakeClusterName, 527 server: "https://1.2.3.4", 528 wantRemoteSecret: &v1.Secret{ 529 ObjectMeta: metav1.ObjectMeta{ 530 Name: remoteSecretNameFromClusterName(fakeClusterName), 531 Annotations: map[string]string{ 532 clusterNameAnnotationKey: fakeClusterName, 533 }, 534 Labels: map[string]string{ 535 multicluster.MultiClusterSecretLabel: "true", 536 }, 537 }, 538 Data: map[string][]byte{ 539 fakeClusterName: []byte(kubeconfig), 540 }, 541 }, 542 }, 543 } 544 oldBackoff := tokenWaitBackoff 545 tokenWaitBackoff = time.Millisecond 546 t.Cleanup(func() { 547 tokenWaitBackoff = oldBackoff 548 }) 549 for i := range cases { 550 c := &cases[i] 551 secName := remoteSecretNameFromClusterName(c.clusterName) 552 t.Run(fmt.Sprintf("[%v] %v", i, c.name), func(tt *testing.T) { 553 // no updateTokenSecret means re-fetching yields the same result 554 obj := []runtime.Object{c.haveTokenSecret} 555 if c.updatedTokenSecret != nil { 556 // fetching should give a different result than the token secret we pass in 557 obj = []runtime.Object{c.updatedTokenSecret} 558 } 559 client := kube.NewFakeClient(obj...) 560 561 got, err := createRemoteSecretFromTokenAndServer(client, c.haveTokenSecret, c.clusterName, c.server, secName) 562 if c.wantErrStr != "" { 563 if err == nil { 564 tt.Fatalf("wanted error including %q but none", c.wantErrStr) 565 } else if !strings.Contains(err.Error(), c.wantErrStr) { 566 tt.Fatalf("wanted error including %q but %v", c.wantErrStr, err) 567 } 568 } else if c.wantErrStr == "" && err != nil { 569 tt.Fatalf("wanted non-error but got %q", err) 570 } else if diff := cmp.Diff(got, c.wantRemoteSecret); diff != "" { 571 tt.Fatalf(" got %v\nwant %v\ndiff %v", got, c.wantRemoteSecret, diff) 572 } 573 }) 574 } 575 } 576 577 func TestWriteEncodedSecret(t *testing.T) { 578 s := &v1.Secret{ 579 ObjectMeta: metav1.ObjectMeta{ 580 Name: "foo", 581 }, 582 } 583 584 w := &fakeOutputWriter{failAfter: 0, injectError: errors.New("error")} 585 if err := writeEncodedObject(w, s); err == nil { 586 t.Error("want error on local write failure") 587 } 588 589 w = &fakeOutputWriter{failAfter: 1, injectError: errors.New("error")} 590 if err := writeEncodedObject(w, s); err == nil { 591 t.Error("want error on remote write failure") 592 } 593 594 w = &fakeOutputWriter{failAfter: 2, injectError: errors.New("error")} 595 if err := writeEncodedObject(w, s); err == nil { 596 t.Error("want error on third write failure") 597 } 598 599 w = &fakeOutputWriter{} 600 if err := writeEncodedObject(w, s); err != nil { 601 t.Errorf("unexpected error: %v", err) 602 } 603 604 want := `# This file is autogenerated, do not edit. 605 apiVersion: v1 606 kind: Secret 607 metadata: 608 creationTimestamp: null 609 name: foo 610 --- 611 ` 612 if w.String() != want { 613 t.Errorf("got\n%q\nwant\n%q", w.String(), want) 614 } 615 } 616 617 func TestCreateRemoteSecretFromPlugin(t *testing.T) { 618 fakeClusterName := "fake-clusterName-0" 619 kubeconfig := strings.ReplaceAll(`apiVersion: v1 620 clusters: 621 - cluster: 622 certificate-authority-data: Y2FEYXRh 623 server: https://1.2.3.4 624 name: {cluster} 625 contexts: 626 - context: 627 cluster: {cluster} 628 user: {cluster} 629 name: {cluster} 630 current-context: {cluster} 631 kind: Config 632 preferences: {} 633 users: 634 - name: {cluster} 635 user: 636 auth-provider: 637 config: 638 k1: v1 639 name: foobar 640 `, "{cluster}", fakeClusterName) 641 642 cases := []struct { 643 name string 644 in *v1.Secret 645 context string 646 clusterName string 647 server string 648 authProviderConfig *api.AuthProviderConfig 649 want *v1.Secret 650 wantErrStr string 651 }{ 652 { 653 name: "error on missing caData", 654 in: makeSecret("", "", "token"), 655 context: "c0", 656 clusterName: fakeClusterName, 657 wantErrStr: errMissingRootCAKey.Error(), 658 }, 659 { 660 name: "success on missing token", 661 in: makeSecret("", "caData", ""), 662 context: "c0", 663 clusterName: fakeClusterName, 664 server: "https://1.2.3.4", 665 authProviderConfig: &api.AuthProviderConfig{ 666 Name: "foobar", 667 Config: map[string]string{ 668 "k1": "v1", 669 }, 670 }, 671 want: &v1.Secret{ 672 ObjectMeta: metav1.ObjectMeta{ 673 Name: remoteSecretNameFromClusterName(fakeClusterName), 674 Annotations: map[string]string{ 675 clusterNameAnnotationKey: fakeClusterName, 676 }, 677 Labels: map[string]string{ 678 multicluster.MultiClusterSecretLabel: "true", 679 }, 680 }, 681 Data: map[string][]byte{ 682 fakeClusterName: []byte(kubeconfig), 683 }, 684 }, 685 }, 686 { 687 name: "success", 688 in: makeSecret("", "caData", "token"), 689 context: "c0", 690 clusterName: fakeClusterName, 691 server: "https://1.2.3.4", 692 authProviderConfig: &api.AuthProviderConfig{ 693 Name: "foobar", 694 Config: map[string]string{ 695 "k1": "v1", 696 }, 697 }, 698 want: &v1.Secret{ 699 ObjectMeta: metav1.ObjectMeta{ 700 Name: remoteSecretNameFromClusterName(fakeClusterName), 701 Annotations: map[string]string{ 702 clusterNameAnnotationKey: fakeClusterName, 703 }, 704 Labels: map[string]string{ 705 multicluster.MultiClusterSecretLabel: "true", 706 }, 707 }, 708 Data: map[string][]byte{ 709 fakeClusterName: []byte(kubeconfig), 710 }, 711 }, 712 }, 713 } 714 715 for i := range cases { 716 c := &cases[i] 717 secName := remoteSecretNameFromClusterName(c.clusterName) 718 t.Run(fmt.Sprintf("[%v] %v", i, c.name), func(tt *testing.T) { 719 got, err := createRemoteSecretFromPlugin(c.in, c.server, c.clusterName, secName, c.authProviderConfig) 720 if c.wantErrStr != "" { 721 if err == nil { 722 tt.Fatalf("wanted error including %q but none", c.wantErrStr) 723 } else if !strings.Contains(err.Error(), c.wantErrStr) { 724 tt.Fatalf("wanted error including %q but %v", c.wantErrStr, err) 725 } 726 } else if c.wantErrStr == "" && err != nil { 727 tt.Fatalf("wanted non-error but got %q", err) 728 } else if diff := cmp.Diff(got, c.want); diff != "" { 729 tt.Fatalf(" got %v\nwant %v\ndiff %v", got, c.want, diff) 730 } 731 }) 732 } 733 } 734 735 func TestRemoteSecretOptions(t *testing.T) { 736 g := NewWithT(t) 737 738 ctx := cli.NewFakeContext(nil) 739 o := RemoteSecretOptions{} 740 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 741 o.addFlags(flags) 742 g.Expect(flags.Parse([]string{ 743 "--name", 744 "valid-name", 745 })).Should(Succeed()) 746 g.Expect(o.prepare(ctx)).Should(Succeed()) 747 748 o = RemoteSecretOptions{} 749 flags = pflag.NewFlagSet("test", pflag.ContinueOnError) 750 o.addFlags(flags) 751 g.Expect(flags.Parse([]string{ 752 "--name", 753 "?-invalid-name", 754 })).Should(Succeed()) 755 g.Expect(o.prepare(ctx)).Should(Not(Succeed())) 756 }