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 }