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  }