istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/util_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 kube
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  	"reflect"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/types"
    28  	"k8s.io/client-go/tools/clientcmd/api"
    29  
    30  	networkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3"
    31  	"istio.io/istio/pkg/ptr"
    32  	"istio.io/istio/pkg/test/util/assert"
    33  	"istio.io/istio/pkg/util/sets"
    34  )
    35  
    36  func TestBuildClientConfig(t *testing.T) {
    37  	config1 := generateKubeConfig(t, "1.1.1.1", "3.3.3.3")
    38  	config2 := generateKubeConfig(t, "2.2.2.2", "4.4.4.4")
    39  
    40  	tests := []struct {
    41  		name               string
    42  		explicitKubeconfig string
    43  		envKubeconfig      string
    44  		context            string
    45  		wantErr            bool
    46  		wantHost           string
    47  	}{
    48  		{
    49  			name:               "DefaultSystemKubeconfig",
    50  			explicitKubeconfig: "",
    51  			envKubeconfig:      config1,
    52  			wantErr:            false,
    53  			wantHost:           "https://1.1.1.1:8001",
    54  		},
    55  		{
    56  			name:               "SinglePath",
    57  			explicitKubeconfig: config1,
    58  			wantErr:            false,
    59  			envKubeconfig:      "",
    60  			wantHost:           "https://1.1.1.1:8001",
    61  		},
    62  		{
    63  			name:               "MultiplePathsFirst",
    64  			explicitKubeconfig: "",
    65  			wantErr:            false,
    66  			envKubeconfig:      fmt.Sprintf("%s:%s", config1, config2),
    67  			wantHost:           "https://1.1.1.1:8001",
    68  		},
    69  		{
    70  			name:               "MultiplePathsSecond",
    71  			explicitKubeconfig: "",
    72  			wantErr:            false,
    73  			envKubeconfig:      fmt.Sprintf("missing:%s", config2),
    74  			wantHost:           "https://2.2.2.2:8001",
    75  		},
    76  		{
    77  			name:               "NonCurrentContext",
    78  			explicitKubeconfig: config1,
    79  			wantErr:            false,
    80  			envKubeconfig:      "",
    81  			context:            "cluster2.local-context",
    82  			wantHost:           "https://3.3.3.3:8001",
    83  		},
    84  	}
    85  	for _, tt := range tests {
    86  		t.Run(tt.name, func(t *testing.T) {
    87  			t.Setenv("KUBECONFIG", tt.envKubeconfig)
    88  
    89  			resp, err := BuildClientConfig(tt.explicitKubeconfig, tt.context)
    90  			if (err != nil) != tt.wantErr {
    91  				t.Fatalf("BuildClientConfig() error = %v, wantErr %v", err, tt.wantErr)
    92  			}
    93  			if resp != nil && resp.Host != tt.wantHost {
    94  				t.Fatalf("Incorrect host. Got: %s, Want: %s", resp.Host, tt.wantHost)
    95  			}
    96  		})
    97  	}
    98  }
    99  
   100  func generateKubeConfig(t *testing.T, cluster1Host string, cluster2Host string) string {
   101  	t.Helper()
   102  
   103  	tempDir := t.TempDir()
   104  	filePath := filepath.Join(tempDir, "config")
   105  
   106  	template := `apiVersion: v1
   107  kind: Config
   108  clusters:
   109  - cluster:
   110      insecure-skip-tls-verify: true
   111      server: https://%s:8001
   112    name: cluster.local
   113  - cluster:
   114      insecure-skip-tls-verify: true
   115      server: https://%s:8001
   116    name: cluster2.local
   117  contexts:
   118  - context:
   119      cluster: cluster.local
   120      namespace: default
   121      user: admin
   122    name: cluster.local-context
   123  - context:
   124      cluster: cluster2.local
   125      namespace: default
   126      user: admin
   127    name: cluster2.local-context
   128  current-context: cluster.local-context
   129  preferences: {}
   130  users:
   131  - name: admin
   132    user:
   133      token: sdsddsd`
   134  
   135  	sampleConfig := fmt.Sprintf(template, cluster1Host, cluster2Host)
   136  	err := os.WriteFile(filePath, []byte(sampleConfig), 0o644)
   137  	if err != nil {
   138  		t.Fatal(err)
   139  	}
   140  	return filePath
   141  }
   142  
   143  func TestCronJobMetadata(t *testing.T) {
   144  	tests := []struct {
   145  		name             string
   146  		jobName          string
   147  		wantTypeMetadata metav1.TypeMeta
   148  		wantName         types.NamespacedName
   149  	}{
   150  		{
   151  			name:    "cron-job-name-sec",
   152  			jobName: "sec-1234567890",
   153  			wantTypeMetadata: metav1.TypeMeta{
   154  				Kind:       "CronJob",
   155  				APIVersion: "batch/v1",
   156  			},
   157  			wantName: types.NamespacedName{
   158  				Name: "sec",
   159  			},
   160  		},
   161  		{
   162  			name:    "cron-job-name-min",
   163  			jobName: "min-12345678",
   164  			wantTypeMetadata: metav1.TypeMeta{
   165  				Kind:       "CronJob",
   166  				APIVersion: "batch/v1",
   167  			},
   168  			wantName: types.NamespacedName{
   169  				Name: "min",
   170  			},
   171  		},
   172  		{
   173  			name:    "non-cron-job-name",
   174  			jobName: "job-123",
   175  			wantTypeMetadata: metav1.TypeMeta{
   176  				Kind:       "Job",
   177  				APIVersion: "v1",
   178  			},
   179  			wantName: types.NamespacedName{
   180  				Name: "job-123",
   181  			},
   182  		},
   183  	}
   184  
   185  	for _, tt := range tests {
   186  		controller := true
   187  		t.Run(tt.name, func(t *testing.T) {
   188  			gotObjectMeta, gotTypeMeta := GetDeployMetaFromPod(
   189  				&corev1.Pod{
   190  					ObjectMeta: metav1.ObjectMeta{
   191  						GenerateName: tt.jobName + "-pod",
   192  						OwnerReferences: []metav1.OwnerReference{{
   193  							APIVersion: "v1",
   194  							Controller: &controller,
   195  							Kind:       "Job",
   196  							Name:       tt.jobName,
   197  						}},
   198  					},
   199  				},
   200  			)
   201  			if !reflect.DeepEqual(gotObjectMeta, tt.wantName) {
   202  				t.Errorf("Object metadata got %+v want %+v", gotObjectMeta, tt.wantName)
   203  			}
   204  			if !reflect.DeepEqual(gotTypeMeta, tt.wantTypeMetadata) {
   205  				t.Errorf("Type metadata got %+v want %+v", gotTypeMeta, tt.wantTypeMetadata)
   206  			}
   207  		})
   208  	}
   209  }
   210  
   211  func TestDeployMeta(t *testing.T) {
   212  	tests := []struct {
   213  		name             string
   214  		pod              *corev1.Pod
   215  		wantTypeMetadata metav1.TypeMeta
   216  		wantName         types.NamespacedName
   217  	}{
   218  		{
   219  			name: "deployconfig-name-deploy",
   220  			pod:  podForDeploymentConfig("deploy", true),
   221  			wantTypeMetadata: metav1.TypeMeta{
   222  				Kind:       "DeploymentConfig",
   223  				APIVersion: "v1",
   224  			},
   225  			wantName: types.NamespacedName{
   226  				Name: "deploy",
   227  			},
   228  		},
   229  		{
   230  			name: "deployconfig-name-deploy2",
   231  			pod:  podForDeploymentConfig("deploy2", true),
   232  			wantTypeMetadata: metav1.TypeMeta{
   233  				Kind:       "DeploymentConfig",
   234  				APIVersion: "v1",
   235  			},
   236  			wantName: types.NamespacedName{
   237  				Name: "deploy2",
   238  			},
   239  		},
   240  		{
   241  			name: "non-deployconfig-label",
   242  			pod:  podForDeploymentConfig("dep", false),
   243  			wantTypeMetadata: metav1.TypeMeta{
   244  				Kind:       "ReplicationController",
   245  				APIVersion: "v1",
   246  			},
   247  			wantName: types.NamespacedName{
   248  				Name: "dep-rc",
   249  			},
   250  		},
   251  		{
   252  			name: "argo-rollout",
   253  			pod: &corev1.Pod{
   254  				ObjectMeta: metav1.ObjectMeta{
   255  					GenerateName: "name-6dc78b855c-",
   256  					OwnerReferences: []metav1.OwnerReference{{
   257  						APIVersion: "v1",
   258  						Controller: ptr.Of(true),
   259  						Kind:       "ReplicaSet",
   260  						Name:       "name-6dc78b855c",
   261  					}},
   262  					Labels: map[string]string{
   263  						"rollouts-pod-template-hash": "6dc78b855c",
   264  					},
   265  				},
   266  			},
   267  			wantTypeMetadata: metav1.TypeMeta{
   268  				Kind:       "Rollout",
   269  				APIVersion: "v1alpha1",
   270  			},
   271  			wantName: types.NamespacedName{
   272  				Name: "name",
   273  			},
   274  		},
   275  	}
   276  
   277  	for _, tt := range tests {
   278  		t.Run(tt.name, func(t *testing.T) {
   279  			gotName, gotTypeMeta := GetDeployMetaFromPod(tt.pod)
   280  			assert.Equal(t, gotName, tt.wantName)
   281  			assert.Equal(t, gotTypeMeta, tt.wantTypeMetadata)
   282  		})
   283  	}
   284  }
   285  
   286  func podForDeploymentConfig(deployConfigName string, hasDeployConfigLabel bool) *corev1.Pod {
   287  	controller := true
   288  	labels := make(map[string]string)
   289  	if hasDeployConfigLabel {
   290  		labels["deploymentconfig"] = deployConfigName
   291  	}
   292  	return &corev1.Pod{
   293  		ObjectMeta: metav1.ObjectMeta{
   294  			GenerateName: deployConfigName + "-rc-pod",
   295  			OwnerReferences: []metav1.OwnerReference{{
   296  				APIVersion: "v1",
   297  				Controller: &controller,
   298  				Kind:       "ReplicationController",
   299  				Name:       deployConfigName + "-rc",
   300  			}},
   301  			Labels: labels,
   302  		},
   303  	}
   304  }
   305  
   306  func TestStripUnusedFields(t *testing.T) {
   307  	tests := []struct {
   308  		name string
   309  		obj  any
   310  		want any
   311  	}{
   312  		{
   313  			name: "transform pods",
   314  			obj: &corev1.Pod{
   315  				ObjectMeta: metav1.ObjectMeta{
   316  					Namespace:   "foo",
   317  					Name:        "bar",
   318  					Labels:      map[string]string{"a": "b"},
   319  					Annotations: map[string]string{"c": "d"},
   320  					ManagedFields: []metav1.ManagedFieldsEntry{
   321  						{
   322  							Manager: "whatever",
   323  						},
   324  					},
   325  				},
   326  			},
   327  			want: &corev1.Pod{
   328  				ObjectMeta: metav1.ObjectMeta{
   329  					Namespace:   "foo",
   330  					Name:        "bar",
   331  					Labels:      map[string]string{"a": "b"},
   332  					Annotations: map[string]string{"c": "d"},
   333  				},
   334  			},
   335  		},
   336  		{
   337  			name: "transform endpoints",
   338  			obj: &corev1.Endpoints{
   339  				ObjectMeta: metav1.ObjectMeta{
   340  					Namespace:   "foo",
   341  					Name:        "bar",
   342  					Labels:      map[string]string{"a": "b"},
   343  					Annotations: map[string]string{"c": "d"},
   344  					ManagedFields: []metav1.ManagedFieldsEntry{
   345  						{
   346  							Manager: "whatever",
   347  						},
   348  					},
   349  				},
   350  			},
   351  			want: &corev1.Endpoints{
   352  				ObjectMeta: metav1.ObjectMeta{
   353  					Namespace:   "foo",
   354  					Name:        "bar",
   355  					Labels:      map[string]string{"a": "b"},
   356  					Annotations: map[string]string{"c": "d"},
   357  				},
   358  			},
   359  		},
   360  		{
   361  			name: "transform virtual services",
   362  			obj: &networkingv1alpha3.VirtualService{
   363  				ObjectMeta: metav1.ObjectMeta{
   364  					Namespace:   "foo",
   365  					Name:        "bar",
   366  					Labels:      map[string]string{"a": "b"},
   367  					Annotations: map[string]string{"c": "d"},
   368  					ManagedFields: []metav1.ManagedFieldsEntry{
   369  						{
   370  							Manager: "whatever",
   371  						},
   372  					},
   373  				},
   374  			},
   375  			want: &networkingv1alpha3.VirtualService{
   376  				ObjectMeta: metav1.ObjectMeta{
   377  					Namespace:   "foo",
   378  					Name:        "bar",
   379  					Labels:      map[string]string{"a": "b"},
   380  					Annotations: map[string]string{"c": "d"},
   381  				},
   382  			},
   383  		},
   384  	}
   385  	for _, tt := range tests {
   386  		t.Run(tt.name, func(t *testing.T) {
   387  			got, _ := StripUnusedFields(tt.obj)
   388  			if !reflect.DeepEqual(got, tt.want) {
   389  				t.Errorf("StripUnusedFields: got %v, want %v", got, tt.want)
   390  			}
   391  		})
   392  	}
   393  }
   394  
   395  func TestSanitizeKubeConfig(t *testing.T) {
   396  	cases := []struct {
   397  		name      string
   398  		config    api.Config
   399  		allowlist sets.String
   400  		want      api.Config
   401  		wantErr   bool
   402  	}{
   403  		{
   404  			name:    "empty",
   405  			config:  api.Config{},
   406  			want:    api.Config{},
   407  			wantErr: false,
   408  		},
   409  		{
   410  			name: "exec",
   411  			config: api.Config{
   412  				AuthInfos: map[string]*api.AuthInfo{
   413  					"default": {
   414  						Exec: &api.ExecConfig{
   415  							Command: "sleep",
   416  						},
   417  					},
   418  				},
   419  			},
   420  			wantErr: true,
   421  		},
   422  		{
   423  			name:      "exec allowlist",
   424  			allowlist: sets.New("exec"),
   425  			config: api.Config{
   426  				AuthInfos: map[string]*api.AuthInfo{
   427  					"default": {
   428  						Exec: &api.ExecConfig{
   429  							Command: "sleep",
   430  						},
   431  					},
   432  				},
   433  			},
   434  			want: api.Config{
   435  				AuthInfos: map[string]*api.AuthInfo{
   436  					"default": {
   437  						Exec: &api.ExecConfig{
   438  							Command: "sleep",
   439  						},
   440  					},
   441  				},
   442  			},
   443  			wantErr: false,
   444  		},
   445  	}
   446  	for _, tt := range cases {
   447  		t.Run(tt.name, func(t *testing.T) {
   448  			err := sanitizeKubeConfig(tt.config, tt.allowlist)
   449  			if (err != nil) != tt.wantErr {
   450  				t.Fatalf("sanitizeKubeConfig() error = %v, wantErr %v", err, tt.wantErr)
   451  			}
   452  			if err != nil {
   453  				return
   454  			}
   455  			if diff := cmp.Diff(tt.config, tt.want); diff != "" {
   456  				t.Fatal(diff)
   457  			}
   458  		})
   459  	}
   460  }