istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/inject/webhook_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 inject 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "io" 22 "net/http" 23 "net/http/httptest" 24 "os" 25 "path/filepath" 26 "reflect" 27 "strings" 28 "testing" 29 30 jsonpatch "github.com/evanphx/json-patch/v5" 31 openshiftv1 "github.com/openshift/api/apps/v1" 32 "google.golang.org/protobuf/types/known/wrapperspb" 33 "k8s.io/api/admission/v1beta1" 34 appsv1 "k8s.io/api/apps/v1" 35 batchv1 "k8s.io/api/batch/v1" 36 corev1 "k8s.io/api/core/v1" 37 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 38 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 39 "k8s.io/apimachinery/pkg/runtime" 40 "k8s.io/apimachinery/pkg/runtime/schema" 41 k8syaml "k8s.io/apimachinery/pkg/util/yaml" 42 "sigs.k8s.io/yaml" 43 44 "istio.io/api/annotation" 45 "istio.io/api/label" 46 meshconfig "istio.io/api/mesh/v1alpha1" 47 v1beta12 "istio.io/api/networking/v1beta1" 48 "istio.io/istio/operator/pkg/manifest" 49 "istio.io/istio/operator/pkg/name" 50 "istio.io/istio/operator/pkg/util/clog" 51 "istio.io/istio/pilot/cmd/pilot-agent/status" 52 "istio.io/istio/pilot/pkg/model" 53 "istio.io/istio/pilot/test/util" 54 "istio.io/istio/pkg/config" 55 "istio.io/istio/pkg/config/mesh" 56 "istio.io/istio/pkg/config/schema/gvk" 57 "istio.io/istio/pkg/monitoring/monitortest" 58 "istio.io/istio/pkg/test" 59 "istio.io/istio/pkg/test/util/file" 60 ) 61 62 const yamlSeparator = "\n---" 63 64 var minimalSidecarTemplate = &Config{ 65 Policy: InjectionPolicyEnabled, 66 DefaultTemplates: []string{SidecarTemplateName}, 67 RawTemplates: map[string]string{SidecarTemplateName: ` 68 spec: 69 initContainers: 70 - name: istio-init 71 containers: 72 - name: istio-proxy 73 volumes: 74 - name: istio-envoy 75 imagePullSecrets: 76 - name: istio-image-pull-secrets 77 `}, 78 } 79 80 func parseToLabelSelector(t *testing.T, selector string) *metav1.LabelSelector { 81 result, err := metav1.ParseToLabelSelector(selector) 82 if err != nil { 83 t.Errorf("Invalid selector %v: %v", selector, err) 84 } 85 86 return result 87 } 88 89 func TestInjectRequired(t *testing.T) { 90 podSpec := &corev1.PodSpec{} 91 podSpecHostNetwork := &corev1.PodSpec{ 92 HostNetwork: true, 93 } 94 cases := []struct { 95 config *Config 96 podSpec *corev1.PodSpec 97 meta metav1.ObjectMeta 98 want bool 99 }{ 100 { 101 config: &Config{ 102 Policy: InjectionPolicyEnabled, 103 }, 104 podSpec: podSpec, 105 meta: metav1.ObjectMeta{ 106 Name: "no-policy", 107 Namespace: "test-namespace", 108 Annotations: map[string]string{}, 109 }, 110 want: true, 111 }, 112 { 113 config: &Config{ 114 Policy: InjectionPolicyEnabled, 115 }, 116 podSpec: podSpec, 117 meta: metav1.ObjectMeta{ 118 Name: "default-policy", 119 Namespace: "test-namespace", 120 }, 121 want: true, 122 }, 123 { 124 config: &Config{ 125 Policy: InjectionPolicyEnabled, 126 }, 127 podSpec: podSpec, 128 meta: metav1.ObjectMeta{ 129 Name: "force-on-policy", 130 Namespace: "test-namespace", 131 Annotations: map[string]string{annotation.SidecarInject.Name: "true"}, 132 }, 133 want: true, 134 }, 135 { 136 config: &Config{ 137 Policy: InjectionPolicyEnabled, 138 }, 139 podSpec: podSpec, 140 meta: metav1.ObjectMeta{ 141 Name: "force-off-policy", 142 Namespace: "test-namespace", 143 Annotations: map[string]string{annotation.SidecarInject.Name: "false"}, 144 }, 145 want: false, 146 }, 147 { 148 config: &Config{ 149 Policy: InjectionPolicyDisabled, 150 }, 151 podSpec: podSpec, 152 meta: metav1.ObjectMeta{ 153 Name: "no-policy", 154 Namespace: "test-namespace", 155 Annotations: map[string]string{}, 156 }, 157 want: false, 158 }, 159 { 160 config: &Config{ 161 Policy: InjectionPolicyDisabled, 162 }, 163 podSpec: podSpec, 164 meta: metav1.ObjectMeta{ 165 Name: "default-policy", 166 Namespace: "test-namespace", 167 }, 168 want: false, 169 }, 170 { 171 config: &Config{ 172 Policy: InjectionPolicyDisabled, 173 }, 174 podSpec: podSpec, 175 meta: metav1.ObjectMeta{ 176 Name: "force-on-policy", 177 Namespace: "test-namespace", 178 Annotations: map[string]string{annotation.SidecarInject.Name: "true"}, 179 }, 180 want: true, 181 }, 182 { 183 config: &Config{ 184 Policy: InjectionPolicyDisabled, 185 }, 186 podSpec: podSpec, 187 meta: metav1.ObjectMeta{ 188 Name: "force-off-policy", 189 Namespace: "test-namespace", 190 Annotations: map[string]string{annotation.SidecarInject.Name: "false"}, 191 }, 192 want: false, 193 }, 194 { 195 config: &Config{ 196 Policy: InjectionPolicyEnabled, 197 }, 198 podSpec: podSpecHostNetwork, 199 meta: metav1.ObjectMeta{ 200 Name: "force-off-policy", 201 Namespace: "test-namespace", 202 Annotations: map[string]string{}, 203 }, 204 want: false, 205 }, 206 { 207 config: &Config{ 208 Policy: "wrong_policy", 209 }, 210 podSpec: podSpec, 211 meta: metav1.ObjectMeta{ 212 Name: "wrong-policy", 213 Namespace: "test-namespace", 214 Annotations: map[string]string{}, 215 }, 216 want: false, 217 }, 218 { 219 config: &Config{ 220 Policy: InjectionPolicyEnabled, 221 AlwaysInjectSelector: []metav1.LabelSelector{{MatchLabels: map[string]string{"foo": "bar"}}}, 222 }, 223 podSpec: podSpec, 224 meta: metav1.ObjectMeta{ 225 Name: "policy-enabled-always-inject-no-labels", 226 Namespace: "test-namespace", 227 }, 228 want: true, 229 }, 230 { 231 config: &Config{ 232 Policy: InjectionPolicyEnabled, 233 AlwaysInjectSelector: []metav1.LabelSelector{{MatchLabels: map[string]string{"foo": "bar"}}}, 234 }, 235 podSpec: podSpec, 236 meta: metav1.ObjectMeta{ 237 Name: "policy-enabled-always-inject-with-labels", 238 Namespace: "test-namespace", 239 Labels: map[string]string{"foo": "bar1"}, 240 }, 241 want: true, 242 }, 243 { 244 config: &Config{ 245 Policy: InjectionPolicyDisabled, 246 AlwaysInjectSelector: []metav1.LabelSelector{{MatchLabels: map[string]string{"foo": "bar"}}}, 247 }, 248 podSpec: podSpec, 249 meta: metav1.ObjectMeta{ 250 Name: "policy-disabled-always-inject-no-labels", 251 Namespace: "test-namespace", 252 }, 253 want: false, 254 }, 255 { 256 config: &Config{ 257 Policy: InjectionPolicyDisabled, 258 AlwaysInjectSelector: []metav1.LabelSelector{{MatchLabels: map[string]string{"foo": "bar"}}}, 259 }, 260 podSpec: podSpec, 261 meta: metav1.ObjectMeta{ 262 Name: "policy-disabled-always-inject-with-labels", 263 Namespace: "test-namespace", 264 Labels: map[string]string{"foo": "bar"}, 265 }, 266 want: true, 267 }, 268 { 269 config: &Config{ 270 Policy: InjectionPolicyEnabled, 271 NeverInjectSelector: []metav1.LabelSelector{{MatchLabels: map[string]string{"foo": "bar"}}}, 272 }, 273 podSpec: podSpec, 274 meta: metav1.ObjectMeta{ 275 Name: "policy-enabled-never-inject-no-labels", 276 Namespace: "test-namespace", 277 }, 278 want: true, 279 }, 280 { 281 config: &Config{ 282 Policy: InjectionPolicyEnabled, 283 NeverInjectSelector: []metav1.LabelSelector{{MatchLabels: map[string]string{"foo": "bar"}}}, 284 }, 285 podSpec: podSpec, 286 meta: metav1.ObjectMeta{ 287 Name: "policy-enabled-never-inject-with-labels", 288 Namespace: "test-namespace", 289 Labels: map[string]string{"foo": "bar"}, 290 }, 291 want: false, 292 }, 293 { 294 config: &Config{ 295 Policy: InjectionPolicyDisabled, 296 NeverInjectSelector: []metav1.LabelSelector{{MatchLabels: map[string]string{"foo": "bar"}}}, 297 }, 298 podSpec: podSpec, 299 meta: metav1.ObjectMeta{ 300 Name: "policy-disabled-never-inject-no-labels", 301 Namespace: "test-namespace", 302 }, 303 want: false, 304 }, 305 { 306 config: &Config{ 307 Policy: InjectionPolicyDisabled, 308 NeverInjectSelector: []metav1.LabelSelector{{MatchLabels: map[string]string{"foo": "bar"}}}, 309 }, 310 podSpec: podSpec, 311 meta: metav1.ObjectMeta{ 312 Name: "policy-disabled-never-inject-with-labels", 313 Namespace: "test-namespace", 314 Labels: map[string]string{"foo": "bar"}, 315 }, 316 want: false, 317 }, 318 { 319 config: &Config{ 320 Policy: InjectionPolicyEnabled, 321 NeverInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "foo")}, 322 }, 323 podSpec: podSpec, 324 meta: metav1.ObjectMeta{ 325 Name: "policy-enabled-never-inject-with-empty-label", 326 Namespace: "test-namespace", 327 Labels: map[string]string{"foo": "", "foo2": "bar2"}, 328 }, 329 want: false, 330 }, 331 { 332 config: &Config{ 333 Policy: InjectionPolicyDisabled, 334 AlwaysInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "foo")}, 335 }, 336 podSpec: podSpec, 337 meta: metav1.ObjectMeta{ 338 Name: "policy-disabled-always-inject-with-empty-label", 339 Namespace: "test-namespace", 340 Labels: map[string]string{"foo": "", "foo2": "bar2"}, 341 }, 342 want: true, 343 }, 344 { 345 config: &Config{ 346 Policy: InjectionPolicyDisabled, 347 AlwaysInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "always")}, 348 NeverInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "never")}, 349 }, 350 podSpec: podSpec, 351 meta: metav1.ObjectMeta{ 352 Name: "policy-disabled-always-never-inject-with-label-returns-true", 353 Namespace: "test-namespace", 354 Labels: map[string]string{"always": "bar", "foo2": "bar2"}, 355 }, 356 want: true, 357 }, 358 { 359 config: &Config{ 360 Policy: InjectionPolicyDisabled, 361 AlwaysInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "always")}, 362 NeverInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "never")}, 363 }, 364 podSpec: podSpec, 365 meta: metav1.ObjectMeta{ 366 Name: "policy-disabled-always-never-inject-with-label-returns-false", 367 Namespace: "test-namespace", 368 Labels: map[string]string{"never": "bar", "foo2": "bar2"}, 369 }, 370 want: false, 371 }, 372 { 373 config: &Config{ 374 Policy: InjectionPolicyDisabled, 375 AlwaysInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "always")}, 376 NeverInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "never")}, 377 }, 378 podSpec: podSpec, 379 meta: metav1.ObjectMeta{ 380 Name: "policy-disabled-always-never-inject-with-both-labels", 381 Namespace: "test-namespace", 382 Labels: map[string]string{"always": "bar", "never": "bar2"}, 383 }, 384 want: false, 385 }, 386 { 387 config: &Config{ 388 Policy: InjectionPolicyEnabled, 389 NeverInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "foo")}, 390 }, 391 podSpec: podSpec, 392 meta: metav1.ObjectMeta{ 393 Name: "policy-enabled-annotation-true-never-inject", 394 Namespace: "test-namespace", 395 Annotations: map[string]string{annotation.SidecarInject.Name: "true"}, 396 Labels: map[string]string{"foo": "", "foo2": "bar2"}, 397 }, 398 want: true, 399 }, 400 { 401 config: &Config{ 402 Policy: InjectionPolicyEnabled, 403 AlwaysInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "foo")}, 404 }, 405 podSpec: podSpec, 406 meta: metav1.ObjectMeta{ 407 Name: "policy-enabled-annotation-false-always-inject", 408 Namespace: "test-namespace", 409 Annotations: map[string]string{annotation.SidecarInject.Name: "false"}, 410 Labels: map[string]string{"foo": "", "foo2": "bar2"}, 411 }, 412 want: false, 413 }, 414 { 415 config: &Config{ 416 Policy: InjectionPolicyDisabled, 417 AlwaysInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "foo")}, 418 }, 419 podSpec: podSpec, 420 meta: metav1.ObjectMeta{ 421 Name: "policy-disabled-annotation-false-always-inject", 422 Namespace: "test-namespace", 423 Annotations: map[string]string{annotation.SidecarInject.Name: "false"}, 424 Labels: map[string]string{"foo": "", "foo2": "bar2"}, 425 }, 426 want: false, 427 }, 428 { 429 config: &Config{ 430 Policy: InjectionPolicyEnabled, 431 NeverInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "foo"), *parseToLabelSelector(t, "bar")}, 432 }, 433 podSpec: podSpec, 434 meta: metav1.ObjectMeta{ 435 Name: "policy-enabled-never-inject-multiple-labels", 436 Namespace: "test-namespace", 437 Labels: map[string]string{"label1": "", "bar": "anything"}, 438 }, 439 want: false, 440 }, 441 { 442 config: &Config{ 443 Policy: InjectionPolicyDisabled, 444 AlwaysInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "foo"), *parseToLabelSelector(t, "bar")}, 445 }, 446 podSpec: podSpec, 447 meta: metav1.ObjectMeta{ 448 Name: "policy-enabled-always-inject-multiple-labels", 449 Namespace: "test-namespace", 450 Labels: map[string]string{"label1": "", "bar": "anything"}, 451 }, 452 want: true, 453 }, 454 { 455 config: &Config{ 456 Policy: InjectionPolicyDisabled, 457 NeverInjectSelector: []metav1.LabelSelector{*parseToLabelSelector(t, "foo")}, 458 }, 459 podSpec: podSpec, 460 meta: metav1.ObjectMeta{ 461 Name: "policy-disabled-annotation-true-never-inject", 462 Namespace: "test-namespace", 463 Annotations: map[string]string{annotation.SidecarInject.Name: "true"}, 464 Labels: map[string]string{"foo": "", "foo2": "bar2"}, 465 }, 466 want: true, 467 }, 468 { 469 config: &Config{ 470 Policy: InjectionPolicyDisabled, 471 }, 472 podSpec: podSpec, 473 meta: metav1.ObjectMeta{ 474 Name: "policy-disabled-label-enabled", 475 Namespace: "test-namespace", 476 Labels: map[string]string{label.SidecarInject.Name: "true"}, 477 }, 478 want: true, 479 }, 480 { 481 config: &Config{ 482 Policy: InjectionPolicyDisabled, 483 }, 484 podSpec: podSpec, 485 meta: metav1.ObjectMeta{ 486 Name: "policy-disabled-both-enabled", 487 Namespace: "test-namespace", 488 Annotations: map[string]string{annotation.SidecarInject.Name: "true"}, 489 Labels: map[string]string{label.SidecarInject.Name: "true"}, 490 }, 491 want: true, 492 }, 493 { 494 config: &Config{ 495 Policy: InjectionPolicyDisabled, 496 }, 497 podSpec: podSpec, 498 meta: metav1.ObjectMeta{ 499 Name: "policy-disabled-label-enabled-annotation-disabled", 500 Namespace: "test-namespace", 501 Annotations: map[string]string{annotation.SidecarInject.Name: "false"}, 502 Labels: map[string]string{label.SidecarInject.Name: "true"}, 503 }, 504 want: true, 505 }, 506 { 507 config: &Config{ 508 Policy: InjectionPolicyDisabled, 509 }, 510 podSpec: podSpec, 511 meta: metav1.ObjectMeta{ 512 Name: "policy-disabled-label-disabled-annotation-enabled", 513 Namespace: "test-namespace", 514 Annotations: map[string]string{annotation.SidecarInject.Name: "true"}, 515 Labels: map[string]string{label.SidecarInject.Name: "false"}, 516 }, 517 want: false, 518 }, 519 } 520 521 for _, c := range cases { 522 if got := injectRequired(IgnoredNamespaces.UnsortedList(), c.config, c.podSpec, c.meta); got != c.want { 523 t.Errorf("injectRequired(%v, %v) got %v want %v", c.config, c.meta, got, c.want) 524 } 525 } 526 } 527 528 func simulateOwnerRef(m metav1.ObjectMeta, name string, gvk schema.GroupVersionKind) metav1.ObjectMeta { 529 controller := true 530 m.GenerateName = name 531 m.OwnerReferences = []metav1.OwnerReference{{ 532 APIVersion: gvk.GroupVersion().String(), 533 Kind: gvk.Kind, 534 Name: name, 535 Controller: &controller, 536 }} 537 return m 538 } 539 540 func objectToPod(t testing.TB, obj runtime.Object) *corev1.Pod { 541 gvk := obj.GetObjectKind().GroupVersionKind() 542 defaultConversion := func(template corev1.PodTemplateSpec, name string) *corev1.Pod { 543 template.ObjectMeta = simulateOwnerRef(template.ObjectMeta, name, gvk) 544 return &corev1.Pod{ 545 ObjectMeta: template.ObjectMeta, 546 Spec: template.Spec, 547 } 548 } 549 switch o := obj.(type) { 550 case *corev1.Pod: 551 return o 552 case *batchv1.CronJob: 553 o.Spec.JobTemplate.Spec.Template.ObjectMeta = simulateOwnerRef(o.Spec.JobTemplate.Spec.Template.ObjectMeta, o.Name, gvk) 554 return &corev1.Pod{ 555 ObjectMeta: o.Spec.JobTemplate.Spec.Template.ObjectMeta, 556 Spec: o.Spec.JobTemplate.Spec.Template.Spec, 557 } 558 case *appsv1.DaemonSet: 559 return defaultConversion(o.Spec.Template, o.Name) 560 case *appsv1.ReplicaSet: 561 return defaultConversion(o.Spec.Template, o.Name) 562 case *corev1.ReplicationController: 563 return defaultConversion(*o.Spec.Template, o.Name) 564 case *appsv1.StatefulSet: 565 return defaultConversion(o.Spec.Template, o.Name) 566 case *batchv1.Job: 567 return defaultConversion(o.Spec.Template, o.Name) 568 case *openshiftv1.DeploymentConfig: 569 return defaultConversion(*o.Spec.Template, o.Name) 570 case *appsv1.Deployment: 571 // Deployment is special since its a double nested resource 572 rsgvk := schema.GroupVersionKind{Kind: "ReplicaSet", Group: "apps", Version: "v1"} 573 o.Spec.Template.ObjectMeta = simulateOwnerRef(o.Spec.Template.ObjectMeta, o.Name+"-fake", rsgvk) 574 o.Spec.Template.ObjectMeta.GenerateName += "-" 575 if o.Spec.Template.ObjectMeta.Labels == nil { 576 o.Spec.Template.ObjectMeta.Labels = map[string]string{} 577 } 578 o.Spec.Template.ObjectMeta.Labels["pod-template-hash"] = "fake" 579 return &corev1.Pod{ 580 ObjectMeta: o.Spec.Template.ObjectMeta, 581 Spec: o.Spec.Template.Spec, 582 } 583 } 584 t.Fatalf("unknown type: %T", obj) 585 return nil 586 } 587 588 func readInjectionSettings(t testing.TB, fname string) (*Config, ValuesConfig, *meshconfig.MeshConfig) { 589 values := file.AsStringOrFail(t, filepath.Join("testdata", "inputs", fname+".values.gen.yaml")) 590 template := file.AsBytesOrFail(t, filepath.Join("testdata", "inputs", fname+".template.gen.yaml")) 591 meshc := file.AsStringOrFail(t, filepath.Join("testdata", "inputs", fname+".mesh.gen.yaml")) 592 593 vc, err := NewValuesConfig(values) 594 if err != nil { 595 t.Fatal(err) 596 } 597 598 cfg, err := UnmarshalConfig(template) 599 if err != nil { 600 t.Fatalf("failed to unmarshal injectionConfig: %v", err) 601 } 602 meshConfig, err := mesh.ApplyMeshConfig(meshc, mesh.DefaultMeshConfig()) 603 if err != nil { 604 t.Fatalf("failed to unmarshal meshconfig: %v", err) 605 } 606 607 return &cfg, vc, meshConfig 608 } 609 610 func cleanupOldFiles(t testing.TB) { 611 files, err := filepath.Glob(filepath.Join("testdata", "inputs", "*.yaml")) 612 if err != nil { 613 t.Fatal(err) 614 } 615 for _, f := range files { 616 if err := os.Remove(f); err != nil { 617 t.Fatal(err) 618 } 619 } 620 } 621 622 // loadInjectionSettings will render the charts using the operator, with given yaml overrides. 623 // This allows us to fully simulate what will actually happen at run time. 624 func writeInjectionSettings(t testing.TB, fname string, setFlags []string, inFilePath string) { 625 // add --set installPackagePath=<path to charts snapshot> 626 setFlags = append(setFlags, "installPackagePath="+defaultInstallPackageDir(), "profile=empty", "components.pilot.enabled=true") 627 var inFilenames []string 628 if inFilePath != "" { 629 inFilenames = []string{"testdata/inject/" + inFilePath} 630 } 631 632 l := clog.NewConsoleLogger(os.Stdout, os.Stderr, nil) 633 manifests, _, err := manifest.GenManifests(inFilenames, setFlags, false, nil, nil, l) 634 if err != nil { 635 t.Fatalf("failed to generate manifests: %v", err) 636 } 637 for _, mlist := range manifests[name.PilotComponentName] { 638 for _, object := range strings.Split(mlist, yamlSeparator) { 639 if len(object) == 0 { 640 continue 641 } 642 r := bytes.NewReader([]byte(object)) 643 decoder := k8syaml.NewYAMLOrJSONDecoder(r, 1024) 644 645 out := &unstructured.Unstructured{} 646 err := decoder.Decode(out) 647 if err != nil { 648 t.Fatalf("error decoding object %q: %v", object, err) 649 } 650 if out.GetName() == "istio-sidecar-injector" && (out.GroupVersionKind() == schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}) { 651 data, ok := out.Object["data"].(map[string]any) 652 if !ok { 653 t.Fatalf("failed to convert %v", out) 654 } 655 config, ok := data["config"].(string) 656 if !ok { 657 t.Fatalf("failed to config %v", data) 658 } 659 vs, ok := data["values"].(string) 660 if !ok { 661 t.Fatalf("failed to config %v", data) 662 } 663 if err := os.WriteFile(filepath.Join("testdata", "inputs", fname+".values.gen.yaml"), []byte(vs), 0o644); err != nil { 664 t.Fatal(err) 665 } 666 if err := os.WriteFile(filepath.Join("testdata", "inputs", fname+".template.gen.yaml"), []byte(config), 0o644); err != nil { 667 t.Fatal(err) 668 } 669 } else if out.GetName() == "istio" && (out.GroupVersionKind() == schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}) { 670 data, ok := out.Object["data"].(map[string]any) 671 if !ok { 672 t.Fatalf("failed to convert %v", out) 673 } 674 meshdata, ok := data["mesh"].(string) 675 if !ok { 676 t.Fatalf("failed to get meshconfig %v", data) 677 } 678 if err := os.WriteFile(filepath.Join("testdata", "inputs", fname+".mesh.gen.yaml"), []byte(meshdata), 0o644); err != nil { 679 t.Fatal(err) 680 } 681 } 682 } 683 } 684 } 685 686 func splitYamlFile(yamlFile string, t *testing.T) [][]byte { 687 t.Helper() 688 yamlBytes := util.ReadFile(t, yamlFile) 689 return splitYamlBytes(yamlBytes, t) 690 } 691 692 func splitYamlBytes(yaml []byte, t *testing.T) [][]byte { 693 t.Helper() 694 stringParts := strings.Split(string(yaml), yamlSeparator) 695 byteParts := make([][]byte, 0) 696 for _, stringPart := range stringParts { 697 byteParts = append(byteParts, getInjectableYamlDocs(stringPart, t)...) 698 } 699 if len(byteParts) == 0 { 700 t.Fatalf("Found no injectable parts") 701 } 702 return byteParts 703 } 704 705 func getInjectableYamlDocs(yamlDoc string, t *testing.T) [][]byte { 706 t.Helper() 707 m := make(map[string]any) 708 if err := yaml.Unmarshal([]byte(yamlDoc), &m); err != nil { 709 t.Fatal(err) 710 } 711 switch m["kind"] { 712 case "Deployment", "DeploymentConfig", "DaemonSet", "StatefulSet", "Job", "ReplicaSet", 713 "ReplicationController", "CronJob", "Pod": 714 return [][]byte{[]byte(yamlDoc)} 715 case "List": 716 // Split apart the list into separate yaml documents. 717 out := make([][]byte, 0) 718 list := metav1.List{} 719 if err := yaml.Unmarshal([]byte(yamlDoc), &list); err != nil { 720 t.Fatal(err) 721 } 722 for _, i := range list.Items { 723 iout, err := yaml.Marshal(i) 724 if err != nil { 725 t.Fatal(err) 726 } 727 injectables := getInjectableYamlDocs(string(iout), t) 728 out = append(out, injectables...) 729 } 730 return out 731 default: 732 // No injectable parts. 733 return [][]byte{} 734 } 735 } 736 737 func convertToJSON(i any, t test.Failer) []byte { 738 t.Helper() 739 outputJSON, err := json.Marshal(i) 740 if err != nil { 741 t.Fatal(err) 742 } 743 return prettyJSON(outputJSON, t) 744 } 745 746 func prettyJSON(inputJSON []byte, t test.Failer) []byte { 747 t.Helper() 748 // Pretty-print the JSON 749 var prettyBuffer bytes.Buffer 750 if err := json.Indent(&prettyBuffer, inputJSON, "", " "); err != nil { 751 t.Fatalf(err.Error()) 752 } 753 return prettyBuffer.Bytes() 754 } 755 756 func applyJSONPatch(input, patch []byte, t *testing.T) []byte { 757 t.Helper() 758 p, err := jsonpatch.DecodePatch(patch) 759 if err != nil { 760 t.Fatal(err) 761 } 762 763 patchedJSON, err := p.Apply(input) 764 if err != nil { 765 t.Fatal(err) 766 } 767 return prettyJSON(patchedJSON, t) 768 } 769 770 func jsonToUnstructured(obj []byte, t *testing.T) *unstructured.Unstructured { 771 r := bytes.NewReader(obj) 772 decoder := k8syaml.NewYAMLOrJSONDecoder(r, 1024) 773 774 out := &unstructured.Unstructured{} 775 err := decoder.Decode(out) 776 if err != nil { 777 t.Fatalf("error decoding object: %v", err) 778 } 779 return out 780 } 781 782 func normalizeAndCompareDeployments(got, want *corev1.Pod, ignoreIstioMetaJSONAnnotationsEnv bool, t *testing.T) error { 783 t.Helper() 784 // Scrub unimportant fields that tend to differ. 785 delete(got.Annotations, annotation.SidecarStatus.Name) 786 delete(want.Annotations, annotation.SidecarStatus.Name) 787 788 for _, c := range got.Spec.Containers { 789 for _, env := range c.Env { 790 if env.ValueFrom != nil && env.ValueFrom.FieldRef != nil { 791 env.ValueFrom.FieldRef.APIVersion = "" 792 } 793 // check if metajson is encoded correctly 794 if strings.HasPrefix(env.Name, "ISTIO_METAJSON_") { 795 var mm map[string]string 796 if err := json.Unmarshal([]byte(env.Value), &mm); err != nil { 797 t.Fatalf("unable to unmarshal %s: %v", env.Value, err) 798 } 799 } 800 } 801 } 802 803 if ignoreIstioMetaJSONAnnotationsEnv { 804 removeContainerEnvEntry(got, "ISTIO_METAJSON_ANNOTATIONS") 805 removeContainerEnvEntry(want, "ISTIO_METAJSON_ANNOTATIONS") 806 } 807 808 gotString, err := json.Marshal(got) 809 if err != nil { 810 t.Fatal(err) 811 } 812 wantString, err := json.Marshal(want) 813 if err != nil { 814 t.Fatal(err) 815 } 816 817 return util.Compare(gotString, wantString) 818 } 819 820 func removeContainerEnvEntry(pod *corev1.Pod, envVarName string) { 821 for i, c := range pod.Spec.Containers { 822 for j, v := range c.Env { 823 if v.Name == envVarName { 824 pod.Spec.Containers[i].Env = append(c.Env[:j], c.Env[j+1:]...) 825 break 826 } 827 } 828 } 829 } 830 831 func makeTestData(t testing.TB, skip bool, apiVersion string) []byte { 832 t.Helper() 833 834 pod := corev1.Pod{ 835 ObjectMeta: metav1.ObjectMeta{ 836 Name: "test", 837 Annotations: map[string]string{}, 838 }, 839 Spec: corev1.PodSpec{ 840 Volumes: []corev1.Volume{{Name: "v0"}}, 841 InitContainers: []corev1.Container{{Name: "c0"}}, 842 Containers: []corev1.Container{{Name: "c1"}}, 843 ImagePullSecrets: []corev1.LocalObjectReference{{Name: "p0"}}, 844 }, 845 } 846 847 if skip { 848 pod.ObjectMeta.Annotations[annotation.SidecarInject.Name] = "false" 849 } 850 851 raw, err := json.Marshal(&pod) 852 if err != nil { 853 t.Fatalf("Could not create test pod: %v", err) 854 } 855 856 review := v1beta1.AdmissionReview{ 857 TypeMeta: metav1.TypeMeta{ 858 Kind: "AdmissionReview", 859 APIVersion: fmt.Sprintf("admission.k8s.io/%s", apiVersion), 860 }, 861 Request: &v1beta1.AdmissionRequest{ 862 Kind: metav1.GroupVersionKind{ 863 Group: v1beta1.GroupName, 864 Version: apiVersion, 865 Kind: "AdmissionRequest", 866 }, 867 Object: runtime.RawExtension{ 868 Raw: raw, 869 }, 870 Operation: v1beta1.Create, 871 }, 872 } 873 reviewJSON, err := json.Marshal(review) 874 if err != nil { 875 t.Fatalf("Failed to create AdmissionReview: %v", err) 876 } 877 return reviewJSON 878 } 879 880 func createWebhook(t testing.TB, cfg *Config, pcResources int) *Webhook { 881 t.Helper() 882 dir := t.TempDir() 883 884 configBytes, err := yaml.Marshal(cfg) 885 if err != nil { 886 t.Fatalf("Could not marshal test injection config: %v", err) 887 } 888 _, values, _ := readInjectionSettings(t, "default") 889 var ( 890 configFile = filepath.Join(dir, "config-file.yaml") 891 valuesFile = filepath.Join(dir, "values-file.yaml") 892 port = 0 893 ) 894 895 if err := os.WriteFile(configFile, configBytes, 0o644); err != nil { // nolint: vetshadow 896 t.Fatalf("WriteFile(%v) failed: %v", configFile, err) 897 } 898 899 if err := os.WriteFile(valuesFile, []byte(values.raw), 0o644); err != nil { // nolint: vetshadow 900 t.Fatalf("WriteFile(%v) failed: %v", valuesFile, err) 901 } 902 903 // mesh config 904 m := mesh.DefaultMeshConfig() 905 store := model.NewFakeStore() 906 for i := 0; i < pcResources; i++ { 907 store.Create(newProxyConfig(fmt.Sprintf("pc-%d", i), "istio-system", &v1beta12.ProxyConfig{ 908 Concurrency: &wrapperspb.Int32Value{Value: int32(i % 5)}, 909 EnvironmentVariables: map[string]string{ 910 fmt.Sprintf("VAR_%d", i): fmt.Sprint(i), 911 }, 912 })) 913 } 914 pcs := model.GetProxyConfigs(store, m) 915 env := model.Environment{ 916 Watcher: mesh.NewFixedWatcher(m), 917 ConfigStore: store, 918 } 919 env.SetPushContext(&model.PushContext{ 920 ProxyConfigs: pcs, 921 }) 922 watcher, err := NewFileWatcher(configFile, valuesFile) 923 if err != nil { 924 t.Fatalf("NewFileWatcher() failed: %v", err) 925 } 926 wh, err := NewWebhook(WebhookParameters{ 927 Watcher: watcher, 928 Port: port, 929 Env: &env, 930 Mux: http.NewServeMux(), 931 }) 932 if err != nil { 933 t.Fatalf("NewWebhook() failed: %v", err) 934 } 935 return wh 936 } 937 938 func TestRunAndServe(t *testing.T) { 939 // TODO: adjust the test to match prod defaults instead of fake defaults. 940 wh := createWebhook(t, minimalSidecarTemplate, 0) 941 stop := make(chan struct{}) 942 defer func() { close(stop) }() 943 wh.Run(stop) 944 945 validReview := makeTestData(t, false, "v1beta1") 946 validReviewV1 := makeTestData(t, false, "v1") 947 skipReview := makeTestData(t, true, "v1beta1") 948 949 cases := []struct { 950 name string 951 body []byte 952 contentType string 953 wantAllowed bool 954 wantStatusCode int 955 }{ 956 { 957 name: "valid", 958 body: validReview, 959 contentType: "application/json", 960 wantAllowed: true, 961 wantStatusCode: http.StatusOK, 962 }, 963 { 964 name: "valid(v1 version)", 965 body: validReviewV1, 966 contentType: "application/json", 967 wantAllowed: true, 968 wantStatusCode: http.StatusOK, 969 }, 970 { 971 name: "skipped", 972 body: skipReview, 973 contentType: "application/json", 974 wantAllowed: true, 975 wantStatusCode: http.StatusOK, 976 }, 977 { 978 name: "wrong content-type", 979 body: validReview, 980 contentType: "application/yaml", 981 wantAllowed: false, 982 wantStatusCode: http.StatusUnsupportedMediaType, 983 }, 984 { 985 name: "bad content", 986 body: []byte{0, 1, 2, 3, 4, 5}, // random data 987 contentType: "application/json", 988 wantAllowed: false, 989 wantStatusCode: http.StatusOK, 990 }, 991 { 992 name: "missing body", 993 contentType: "application/json", 994 wantAllowed: false, 995 wantStatusCode: http.StatusBadRequest, 996 }, 997 } 998 mt := monitortest.New(t) 999 for i, c := range cases { 1000 t.Run(fmt.Sprintf("[%d] %s", i, c.name), func(t *testing.T) { 1001 req := httptest.NewRequest("POST", "http://sidecar-injector/inject", bytes.NewReader(c.body)) 1002 req.Header.Add("Content-Type", c.contentType) 1003 1004 w := httptest.NewRecorder() 1005 wh.serveInject(w, req) 1006 res := w.Result() 1007 1008 if res.StatusCode != c.wantStatusCode { 1009 t.Fatalf("wrong status code: \ngot %v \nwant %v", res.StatusCode, c.wantStatusCode) 1010 } 1011 1012 if res.StatusCode != http.StatusOK { 1013 return 1014 } 1015 1016 gotBody, err := io.ReadAll(res.Body) 1017 if err != nil { 1018 t.Fatalf("could not read body: %v", err) 1019 } 1020 var gotReview v1beta1.AdmissionReview 1021 if err := json.Unmarshal(gotBody, &gotReview); err != nil { 1022 t.Fatalf("could not decode response body: %v", err) 1023 } 1024 if gotReview.Response.Allowed != c.wantAllowed { 1025 t.Fatalf("AdmissionReview.Response.Allowed is wrong : got %v want %v", 1026 gotReview.Response.Allowed, c.wantAllowed) 1027 } 1028 1029 var gotPatch bytes.Buffer 1030 if len(gotReview.Response.Patch) > 0 { 1031 if err := json.Compact(&gotPatch, gotReview.Response.Patch); err != nil { 1032 t.Fatalf(err.Error()) 1033 } 1034 } 1035 }) 1036 } 1037 // Now Validate that metrics are created. 1038 testSideCarInjectorMetrics(mt) 1039 } 1040 1041 func testSideCarInjectorMetrics(mt *monitortest.MetricsTest) { 1042 expected := []string{ 1043 "sidecar_injection_requests_total", 1044 "sidecar_injection_success_total", 1045 "sidecar_injection_skip_total", 1046 "sidecar_injection_failure_total", 1047 } 1048 for _, e := range expected { 1049 mt.Assert(e, nil, monitortest.AtLeast(1)) 1050 } 1051 } 1052 1053 func benchmarkInjectServe(pcs int, b *testing.B) { 1054 sidecarTemplate, _, _ := readInjectionSettings(b, "default") 1055 wh := createWebhook(b, sidecarTemplate, pcs) 1056 1057 stop := make(chan struct{}) 1058 defer func() { close(stop) }() 1059 wh.Run(stop) 1060 1061 body := makeTestData(b, false, "v1beta1") 1062 1063 b.ResetTimer() 1064 for i := 0; i < b.N; i++ { 1065 req := httptest.NewRequest("POST", "http://sidecar-injector/inject", bytes.NewReader(body)) 1066 req.Header.Add("Content-Type", "application/json") 1067 1068 wh.serveInject(httptest.NewRecorder(), req) 1069 } 1070 } 1071 1072 func BenchmarkInjectServePC0(b *testing.B) { 1073 benchmarkInjectServe(0, b) 1074 } 1075 1076 func BenchmarkInjectServePC5(b *testing.B) { 1077 benchmarkInjectServe(5, b) 1078 } 1079 1080 func BenchmarkInjectServePC15(b *testing.B) { 1081 benchmarkInjectServe(15, b) 1082 } 1083 1084 func TestEnablePrometheusAggregation(t *testing.T) { 1085 tests := []struct { 1086 name string 1087 mesh *meshconfig.MeshConfig 1088 anno map[string]string 1089 want bool 1090 }{ 1091 { 1092 "no settings", 1093 nil, 1094 nil, 1095 true, 1096 }, 1097 { 1098 "mesh on", 1099 &meshconfig.MeshConfig{EnablePrometheusMerge: &wrapperspb.BoolValue{Value: true}}, 1100 nil, 1101 true, 1102 }, 1103 { 1104 "mesh off", 1105 &meshconfig.MeshConfig{EnablePrometheusMerge: &wrapperspb.BoolValue{Value: false}}, 1106 nil, 1107 false, 1108 }, 1109 { 1110 "annotation on", 1111 &meshconfig.MeshConfig{EnablePrometheusMerge: &wrapperspb.BoolValue{Value: false}}, 1112 map[string]string{annotation.PrometheusMergeMetrics.Name: "true"}, 1113 true, 1114 }, 1115 { 1116 "annotation off", 1117 &meshconfig.MeshConfig{EnablePrometheusMerge: &wrapperspb.BoolValue{Value: true}}, 1118 map[string]string{annotation.PrometheusMergeMetrics.Name: "false"}, 1119 false, 1120 }, 1121 } 1122 for _, tt := range tests { 1123 t.Run(tt.name, func(t *testing.T) { 1124 if got := enablePrometheusMerge(tt.mesh, tt.anno); got != tt.want { 1125 t.Errorf("enablePrometheusMerge() = %v, want %v", got, tt.want) 1126 } 1127 }) 1128 } 1129 } 1130 1131 func TestParseInjectEnvs(t *testing.T) { 1132 cases := []struct { 1133 name string 1134 in string 1135 want map[string]string 1136 }{ 1137 { 1138 name: "empty", 1139 in: "/", 1140 want: map[string]string{}, 1141 }, 1142 { 1143 name: "no-kv", 1144 in: "/inject", 1145 want: map[string]string{}, 1146 }, 1147 { 1148 name: "no-kv-with-tail", 1149 in: "/inject/", 1150 want: map[string]string{}, 1151 }, 1152 { 1153 name: "one-kv", 1154 in: "/inject/cluster/cluster1", 1155 want: map[string]string{"ISTIO_META_CLUSTER_ID": "cluster1"}, 1156 }, 1157 { 1158 name: "two-kv", 1159 in: "/inject/cluster/cluster1/net/network1/", 1160 want: map[string]string{"ISTIO_META_CLUSTER_ID": "cluster1", "ISTIO_META_NETWORK": "network1"}, 1161 }, 1162 { 1163 name: "kv-with-slashes", 1164 in: "/inject/cluster/cluster--slash--1/net/network--slash--1", 1165 want: map[string]string{"ISTIO_META_CLUSTER_ID": "cluster/1", "ISTIO_META_NETWORK": "network/1"}, 1166 }, 1167 { 1168 name: "not-predefined-kv", 1169 in: "/inject/cluster/cluster1/custom_env/foo", 1170 want: map[string]string{"ISTIO_META_CLUSTER_ID": "cluster1", "CUSTOM_ENV": "foo"}, 1171 }, 1172 { 1173 name: "one-key-without-value", 1174 in: "/inject/cluster", 1175 want: map[string]string{}, 1176 }, 1177 { 1178 name: "key-without-value", 1179 in: "/inject/cluster/cluster1/network", 1180 want: map[string]string{"ISTIO_META_CLUSTER_ID": "cluster1"}, 1181 }, 1182 { 1183 name: "key-with-values-contain-slashes", 1184 in: "/inject/:ENV:cluster=cluster2:ENV:rootpage=/foo/bar", 1185 want: map[string]string{"ISTIO_META_CLUSTER_ID": "cluster2", "ROOTPAGE": "/foo/bar"}, 1186 }, 1187 { 1188 // this is to test the path not following :ENV: format, the 1189 // path will be considered using slash as separator 1190 name: "no-predefined-kv-with-values-contain-ENV-separator", 1191 in: "/inject/rootpage1/value1/rootpage2/:ENV:abcd=efgh", 1192 want: map[string]string{"ROOTPAGE1": "value1", "ROOTPAGE2": ":ENV:abcd=efgh"}, 1193 }, 1194 { 1195 // this is to test the path following :ENV: format, but two variables 1196 // do not have correct format, thus they will be ignored. Eg. :ENV:=abb 1197 // :ENV:=, these two are not correct variables. 1198 name: "no-predefined-kv-with-values-contain-ENV-separator-invalid-format", 1199 in: "/inject/:ENV:rootpage1=efgh:ENV:=abb:ENV:=", 1200 want: map[string]string{"ROOTPAGE1": "efgh"}, 1201 }, 1202 { 1203 // this is to test that the path is an url encoded string, still using 1204 // slash as separators 1205 name: "no-predefined-kv-with-mixed-values", 1206 in: func() string { 1207 req, _ := http.NewRequest(http.MethodGet, 1208 "%2Finject%2Frootpage1%2Ffoo%2Frootpage2%2Fbar", nil) 1209 return req.URL.Path 1210 }(), 1211 want: map[string]string{"ROOTPAGE1": "foo", "ROOTPAGE2": "bar"}, 1212 }, 1213 { 1214 // this is to test that the path is an url encoded string and :ENV: as separator 1215 // eg. /inject/:ENV:rootpage1=/foo/bar:ENV:rootpage2=/bar/toe but url encoded. 1216 name: "no-predefined-kv-with-slashes", 1217 in: func() string { 1218 req, _ := http.NewRequest(http.MethodGet, 1219 "%2Finject%2F%3AENV%3Arootpage1%3D%2Ffoo%2Fbar%3AENV%3Arootpage2%3D%2Fbar%2Ftoe", nil) 1220 return req.URL.Path 1221 }(), 1222 want: map[string]string{"ROOTPAGE1": "/foo/bar", "ROOTPAGE2": "/bar/toe"}, 1223 }, 1224 } 1225 1226 for _, tc := range cases { 1227 t.Run(tc.name, func(t *testing.T) { 1228 actual := parseInjectEnvs(tc.in) 1229 if !reflect.DeepEqual(actual, tc.want) { 1230 t.Fatalf("Expected result %#v, but got %#v", tc.want, actual) 1231 } 1232 }) 1233 } 1234 } 1235 1236 func TestMergeOrAppendProbers(t *testing.T) { 1237 cases := []struct { 1238 name string 1239 perviouslyInjected bool 1240 in []corev1.EnvVar 1241 probers string 1242 want []corev1.EnvVar 1243 }{ 1244 { 1245 name: "Append Prober", 1246 perviouslyInjected: false, 1247 in: []corev1.EnvVar{}, 1248 probers: `{"/app-health/bar/livez":{"httpGet":{"path":"/","port":9000,"scheme":"HTTP"}},` + 1249 `"/app-health/foo/livez":{"httpGet":{"path":"/","port":8000,"scheme":"HTTP"}}}`, 1250 want: []corev1.EnvVar{{ 1251 Name: status.KubeAppProberEnvName, 1252 Value: `{"/app-health/bar/livez":{"httpGet":{"path":"/","port":9000,"scheme":"HTTP"}},` + 1253 `"/app-health/foo/livez":{"httpGet":{"path":"/","port":8000,"scheme":"HTTP"}}}`, 1254 }}, 1255 }, 1256 { 1257 name: "Merge Prober", 1258 perviouslyInjected: true, 1259 in: []corev1.EnvVar{ 1260 { 1261 Name: "TEST_ENV_VAR1", 1262 Value: "value1", 1263 }, 1264 { 1265 Name: status.KubeAppProberEnvName, 1266 Value: `{"/app-health/foo/livez":{"httpGet":{"path":"/","port":8000,"scheme":"HTTP"}}}`, 1267 }, 1268 { 1269 Name: "TEST_ENV_VAR2", 1270 Value: "value2", 1271 }, 1272 }, 1273 probers: `{"/app-health/bar/livez":{"httpGet":{"path":"/","port":9000,"scheme":"HTTP"}}}`, 1274 want: []corev1.EnvVar{ 1275 { 1276 Name: "TEST_ENV_VAR1", 1277 Value: "value1", 1278 }, 1279 { 1280 Name: status.KubeAppProberEnvName, 1281 Value: `{"/app-health/bar/livez":{"httpGet":{"path":"/","port":9000,"scheme":"HTTP"}},` + 1282 `"/app-health/foo/livez":{"httpGet":{"path":"/","port":8000,"scheme":"HTTP"}}}`, 1283 }, 1284 { 1285 Name: "TEST_ENV_VAR2", 1286 Value: "value2", 1287 }, 1288 }, 1289 }, 1290 } 1291 for _, tc := range cases { 1292 t.Run(tc.name, func(t *testing.T) { 1293 actual := mergeOrAppendProbers(tc.perviouslyInjected, tc.in, tc.probers) 1294 if !reflect.DeepEqual(actual, tc.want) { 1295 t.Fatalf("Expected result %#v, but got %#v", tc.want, actual) 1296 } 1297 }) 1298 } 1299 } 1300 1301 func TestParseStatus(t *testing.T) { 1302 cases := []struct { 1303 name string 1304 status string 1305 want ParsedContainers 1306 }{ 1307 { 1308 name: "Regular Containers only", 1309 status: `{"containers":["istio-proxy", "random-container"],` + 1310 `"volumes":["workload-socket","istio-token","istiod-ca-cert"]}`, 1311 want: ParsedContainers{ 1312 Containers: []corev1.Container{ 1313 {Name: "istio-proxy"}, 1314 {Name: "random-container"}, 1315 }, 1316 InitContainers: []corev1.Container{}, 1317 }, 1318 }, 1319 { 1320 name: "Init Containers only", 1321 status: `{"initContainers":["istio-init", "istio-validation"],` + 1322 `"volumes":["workload-socket","istio-token","istiod-ca-cert"]}`, 1323 want: ParsedContainers{ 1324 Containers: []corev1.Container{}, 1325 InitContainers: []corev1.Container{ 1326 {Name: "istio-init"}, 1327 {Name: "istio-validation"}, 1328 }, 1329 }, 1330 }, 1331 { 1332 name: "All Containers", 1333 status: `{"containers":["istio-proxy", "random-container"],"initContainers":["istio-init",` + 1334 ` "istio-validation"],"volumes":["workload-socket","istio-token","istiod-ca-cert"]}`, 1335 want: ParsedContainers{ 1336 Containers: []corev1.Container{ 1337 {Name: "istio-proxy"}, 1338 {Name: "random-container"}, 1339 }, 1340 InitContainers: []corev1.Container{ 1341 {Name: "istio-init"}, 1342 {Name: "istio-validation"}, 1343 }, 1344 }, 1345 }, 1346 { 1347 name: "Containers is null", 1348 status: `{"containers":null,"initContainers":["istio-init",` + 1349 ` "istio-validation"],"volumes":["workload-socket","istio-token","istiod-ca-cert"]}`, 1350 want: ParsedContainers{ 1351 Containers: []corev1.Container{}, 1352 InitContainers: []corev1.Container{ 1353 {Name: "istio-init"}, 1354 {Name: "istio-validation"}, 1355 }, 1356 }, 1357 }, 1358 { 1359 name: "Empty String", 1360 status: ``, 1361 want: ParsedContainers{}, 1362 }, 1363 } 1364 for _, tc := range cases { 1365 t.Run(tc.name, func(t *testing.T) { 1366 actual := parseStatus(tc.status) 1367 1368 if !reflect.DeepEqual(actual, tc.want) { 1369 t.Fatalf("Expected result %#v, but got %#v", tc.want, actual) 1370 } 1371 }) 1372 } 1373 } 1374 1375 func newProxyConfig(name, ns string, spec config.Spec) config.Config { 1376 return config.Config{ 1377 Meta: config.Meta{ 1378 GroupVersionKind: gvk.ProxyConfig, 1379 Name: name, 1380 Namespace: ns, 1381 }, 1382 Spec: spec, 1383 } 1384 } 1385 1386 // defaultInstallPackageDir returns a path to a snapshot of the helm charts used for testing. 1387 func defaultInstallPackageDir() string { 1388 wd, err := os.Getwd() 1389 if err != nil { 1390 panic(err) 1391 } 1392 return filepath.Join(wd, "../../../manifests/") 1393 }