github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/workloadselector/workloadselector_test.go (about) 1 // Copyright (c) 2021, 2022, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package workloadselector 5 6 import ( 7 "context" 8 "testing" 9 10 "github.com/stretchr/testify/assert" 11 corev1 "k8s.io/api/core/v1" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 14 "k8s.io/apimachinery/pkg/runtime" 15 "k8s.io/apimachinery/pkg/runtime/schema" 16 dynamicfake "k8s.io/client-go/dynamic/fake" 17 "k8s.io/client-go/kubernetes/fake" 18 ) 19 20 // TestMatch tests DoesWorkloadMatch 21 // GIVEN a namespace label selector, object selector, and specific GVK values 22 // WHEN DoesWorkloadMatch is called 23 // THEN a match of true is returned 24 func TestMatch(t *testing.T) { 25 ws := &WorkloadSelector{ 26 KubeClient: fake.NewSimpleClientset(), 27 } 28 29 labels := map[string]string{ 30 "test-label": "true", 31 } 32 33 // Create the namespace 34 ws.createNamespace(t, "test-ns", labels) 35 36 // Create a deployment 37 deploy := ws.createDeployment(t, "test-ns", "test-deploy", labels) 38 39 // Namespace selector with match labels 40 namespaceSelector := &metav1.LabelSelector{ 41 MatchLabels: labels, 42 } 43 44 // Object selector with match expressions 45 objectSelector := &metav1.LabelSelector{ 46 MatchExpressions: []metav1.LabelSelectorRequirement{ 47 { 48 Key: "test-label", 49 Operator: metav1.LabelSelectorOpExists, 50 }, 51 }, 52 } 53 54 // workload resource matches 55 found, err := ws.DoesWorkloadMatch(deploy, namespaceSelector, objectSelector, []string{"apps"}, []string{"v1"}, []string{"deployment"}) 56 assert.NoError(t, err, "unexpected error matching resource") 57 assert.True(t, found, "expected to find match") 58 } 59 60 // TestMatchDefaults tests DoesWorkloadMatch 61 // GIVEN no namespace label selector, no object selector, and no GVK values 62 // WHEN DoesWorkloadMatch is called 63 // THEN a match of true is returned 64 func TestMatchDefaults(t *testing.T) { 65 ws := &WorkloadSelector{ 66 KubeClient: fake.NewSimpleClientset(), 67 } 68 69 labels := map[string]string{ 70 "test-label": "true", 71 } 72 73 // Create the namespace 74 ws.createNamespace(t, "test-ns", labels) 75 76 // Create a deployment 77 deploy := ws.createDeployment(t, "test-ns", "test-deploy", labels) 78 79 // workload resource matches 80 found, err := ws.DoesWorkloadMatch(deploy, nil, nil, nil, nil, nil) 81 assert.NoError(t, err, "unexpected error matching resource") 82 assert.True(t, found, "expected to find match") 83 } 84 85 // TestNoMatchNamespace tests DoesWorkloadMatch 86 // GIVEN a namespace label selector, object selector, and specific GVK values 87 // WHEN DoesWorkloadMatch is called 88 // THEN a match of false is returned because namespace did not match 89 func TestNoMatchNamespace(t *testing.T) { 90 ws := &WorkloadSelector{ 91 KubeClient: fake.NewSimpleClientset(), 92 } 93 94 labels := map[string]string{ 95 "test-label": "true", 96 } 97 98 // Create the namespace 99 ws.createNamespace(t, "test-ns", nil) 100 101 // Create a deployment 102 deploy := ws.createDeployment(t, "test-ns", "test-deploy", labels) 103 104 // Namespace selector with match labels 105 namespaceSelector := &metav1.LabelSelector{ 106 MatchLabels: labels, 107 } 108 109 // Object selector with match expressions 110 objectSelector := &metav1.LabelSelector{ 111 MatchExpressions: []metav1.LabelSelectorRequirement{ 112 { 113 Key: "test-label", 114 Operator: metav1.LabelSelectorOpExists, 115 }, 116 }, 117 } 118 119 // workload resource does not match 120 found, err := ws.DoesWorkloadMatch(deploy, namespaceSelector, objectSelector, []string{"apps"}, []string{"v1"}, []string{"deployment"}) 121 assert.NoError(t, err, "unexpected error matching resource") 122 assert.False(t, found, "expected to not find match") 123 } 124 125 // TestEmptyNamespaceSelectorMatch tests doesNamespaceMatch 126 // GIVEN an empty namespace label selector 127 // WHEN doesNamespaceMatch is called 128 // THEN a match of true is returned 129 func TestEmptyNamespaceSelectorMatch(t *testing.T) { 130 ws := &WorkloadSelector{ 131 KubeClient: fake.NewSimpleClientset(), 132 } 133 134 // Create a couple of namespaces 135 ws.createNamespace(t, "default", nil) 136 ws.createNamespace(t, "test-ns", nil) 137 138 // Create a deployment 139 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 140 141 // Empty namespace selector 142 namespaceSelector := &metav1.LabelSelector{} 143 144 // Namespace match found 145 found, err := ws.doesNamespaceMatch(deploy, namespaceSelector) 146 assert.NoError(t, err, "unexpected error getting namespaces") 147 assert.True(t, found, "expected to find match") 148 } 149 150 // TestNilNamespaceSelectorMatch tests doesNamespaceMatch 151 // GIVEN a nil namespace label selector 152 // WHEN doesNamespaceMatch is called 153 // THEN a match of true is returned 154 func TestNilNamespaceSelectorMatch(t *testing.T) { 155 ws := &WorkloadSelector{ 156 KubeClient: fake.NewSimpleClientset(), 157 } 158 159 // Create a couple of namespaces 160 ws.createNamespace(t, "default", nil) 161 ws.createNamespace(t, "test-ns", nil) 162 163 // Create a deployment 164 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 165 166 // Namespace match found - nil namespace selector specified 167 found, err := ws.doesNamespaceMatch(deploy, nil) 168 assert.NoError(t, err, "unexpected error getting namespaces") 169 assert.True(t, found, "expected to find match") 170 } 171 172 // TestMatchLabelsNamespaceSelectorMatch tests doesNamespaceMatch 173 // GIVEN a namespace label selector using a MatchLabel 174 // WHEN doesNamespaceMatch is called 175 // THEN a match of true is returned 176 func TestMatchLabelsNamespaceSelectorMatch(t *testing.T) { 177 ws := &WorkloadSelector{ 178 KubeClient: fake.NewSimpleClientset(), 179 } 180 181 namespaceLabels := map[string]string{ 182 "test-label": "true", 183 } 184 185 // Create a couple of namespaces 186 ws.createNamespace(t, "default", nil) 187 ws.createNamespace(t, "test-ns", namespaceLabels) 188 189 // Create a deployment 190 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 191 192 // Namespace selector with match labels 193 namespaceSelector := &metav1.LabelSelector{ 194 MatchLabels: namespaceLabels, 195 } 196 197 // Namespace match found 198 found, err := ws.doesNamespaceMatch(deploy, namespaceSelector) 199 assert.NoError(t, err, "unexpected error getting namespaces") 200 assert.True(t, found, "expected to find match") 201 } 202 203 // TestMatchLabelsNamespaceSelectorNoMatch tests doesNamespaceMatch 204 // GIVEN a namespace label selector using a MatchLabel 205 // WHEN doesNamespaceMatch is called 206 // THEN a match of false is returned 207 func TestMatchLabelsNamespaceSelectorNoMatch(t *testing.T) { 208 ws := &WorkloadSelector{ 209 KubeClient: fake.NewSimpleClientset(), 210 } 211 212 namespaceLabels := map[string]string{ 213 "test-label": "true", 214 } 215 216 // Create a couple of namespaces 217 ws.createNamespace(t, "default", nil) 218 ws.createNamespace(t, "test-ns", nil) 219 220 // Create a deployment 221 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 222 223 // Namespace selector with match labels 224 namespaceSelector := &metav1.LabelSelector{ 225 MatchLabels: namespaceLabels, 226 } 227 228 // Namespace match not found 229 found, err := ws.doesNamespaceMatch(deploy, namespaceSelector) 230 assert.NoError(t, err, "unexpected error getting namespaces") 231 assert.False(t, found, "expected to not find match") 232 } 233 234 // TestMatchExpressionsNamespaceSelector tests doesNamespaceMatch 235 // GIVEN a namespace label selector using a MatchExpression 236 // WHEN doesNamespaceMatch is called 237 // THEN a match of true is returned 238 func TestMatchExpressionsNamespaceSelector(t *testing.T) { 239 ws := &WorkloadSelector{ 240 KubeClient: fake.NewSimpleClientset(), 241 } 242 243 namespaceLabels := map[string]string{ 244 "test-label": "true", 245 } 246 247 // Create a couple of namespaces 248 ws.createNamespace(t, "default", nil) 249 ws.createNamespace(t, "test-ns", namespaceLabels) 250 251 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 252 253 // Namespace selector with match expressions 254 namespaceSelector := &metav1.LabelSelector{ 255 MatchExpressions: []metav1.LabelSelectorRequirement{ 256 { 257 Key: "test-label", 258 Operator: metav1.LabelSelectorOpExists, 259 }, 260 }, 261 } 262 263 // Namespace selector with match labels 264 found, err := ws.doesNamespaceMatch(deploy, namespaceSelector) 265 assert.NoError(t, err, "unexpected error getting namespaces") 266 assert.True(t, found, "expected to find match") 267 } 268 269 // TestMatchExpressionsNamespaceSelectorNoMatch tests doesNamespaceMatch 270 // GIVEN a namespace label selector using a MatchExpression 271 // WHEN doesNamespaceMatch is called 272 // THEN a match of false is returned 273 func TestMatchExpressionsNamespaceSelectorNoMatch(t *testing.T) { 274 ws := &WorkloadSelector{ 275 KubeClient: fake.NewSimpleClientset(), 276 } 277 278 // Create a couple of namespaces 279 ws.createNamespace(t, "default", nil) 280 ws.createNamespace(t, "test-ns", nil) 281 282 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 283 284 // Namespace selector with match expressions 285 namespaceSelector := &metav1.LabelSelector{ 286 MatchExpressions: []metav1.LabelSelectorRequirement{ 287 { 288 Key: "test-label", 289 Operator: metav1.LabelSelectorOpExists, 290 }, 291 }, 292 } 293 294 // Namespace match not found 295 found, err := ws.doesNamespaceMatch(deploy, namespaceSelector) 296 assert.NoError(t, err, "unexpected error getting namespaces") 297 assert.False(t, found, "expected to not find match") 298 } 299 300 // TestMatchExactGVK tests doesObjectMatch 301 // GIVEN specific GVK values and no object label selector 302 // WHEN doesObjectMatch is called 303 // THEN a match of true is returned 304 func TestMatchExactGVK(t *testing.T) { 305 ws := &WorkloadSelector{ 306 KubeClient: fake.NewSimpleClientset(), 307 } 308 309 // Create deployment 310 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 311 312 // Two namespace 313 ws.createNamespace(t, "default", nil) 314 ws.createNamespace(t, "test-ns", nil) 315 316 // No object selector 317 objectSelector := &metav1.LabelSelector{} 318 319 // workload resource matches 320 found, err := ws.doesObjectMatch(deploy, objectSelector, []string{"apps"}, []string{"v1"}, []string{"deployment"}) 321 assert.NoError(t, err, "unexpected error matching resource") 322 assert.True(t, found, "expected to find match") 323 } 324 325 // TestMatchWildcardVersion tests doesObjectMatch 326 // GIVEN GVK values with a wildcard version and no object label selector 327 // WHEN doesObjectMatch is called 328 // THEN a match of true is returned 329 func TestMatchWildcardVersion(t *testing.T) { 330 ws := &WorkloadSelector{ 331 KubeClient: fake.NewSimpleClientset(), 332 } 333 334 // Create deployment 335 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 336 337 // Two namespace 338 ws.createNamespace(t, "default", nil) 339 ws.createNamespace(t, "test-ns", nil) 340 341 // No object selector 342 objectSelector := &metav1.LabelSelector{} 343 344 // workload resource matches 345 found, err := ws.doesObjectMatch(deploy, objectSelector, []string{"apps"}, []string{"*"}, []string{"deployment"}) 346 assert.NoError(t, err, "unexpected error matching resource") 347 assert.True(t, found, "expected to find match") 348 } 349 350 // TestMatchWildcardGroup tests doesObjectMatch 351 // GIVEN GVK values with a wildcard group and no object label selector 352 // WHEN doesObjectMatch is called 353 // THEN a match of true is returned 354 func TestMatchWildcardGroup(t *testing.T) { 355 ws := &WorkloadSelector{ 356 KubeClient: fake.NewSimpleClientset(), 357 } 358 359 // Create deployment 360 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 361 362 // Two namespace 363 ws.createNamespace(t, "default", nil) 364 ws.createNamespace(t, "test-ns", nil) 365 366 // No object selector 367 objectSelector := &metav1.LabelSelector{} 368 369 // workload resource matches 370 found, err := ws.doesObjectMatch(deploy, objectSelector, []string{"*"}, []string{"v1"}, []string{"deployment"}) 371 assert.NoError(t, err, "unexpected error matching resource") 372 assert.True(t, found, "expected to find match") 373 } 374 375 // TestMatchWildcardKind tests doesObjectMatch 376 // GIVEN GVK values with a wildcard Kind and no object label selector 377 // WHEN doesObjectMatch is called 378 // THEN a match of true is returned 379 func TestMatchWildcardKind(t *testing.T) { 380 ws := &WorkloadSelector{ 381 KubeClient: fake.NewSimpleClientset(), 382 } 383 384 // Create deployment 385 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 386 387 // Two namespace 388 ws.createNamespace(t, "default", nil) 389 ws.createNamespace(t, "test-ns", nil) 390 391 // No object selector 392 objectSelector := &metav1.LabelSelector{} 393 394 // workload resource matches 395 found, err := ws.doesObjectMatch(deploy, objectSelector, []string{"apps"}, []string{"v1"}, []string{"*"}) 396 assert.NoError(t, err, "unexpected error matching resource") 397 assert.True(t, found, "expected to find match") 398 } 399 400 // TestNoMatchExactGVK tests doesObjectMatch 401 // GIVEN specific GVK values and no object label selector 402 // WHEN doesObjectMatch is called 403 // THEN a match of false is returned 404 func TestNoMatchExactGVK(t *testing.T) { 405 ws := &WorkloadSelector{ 406 KubeClient: fake.NewSimpleClientset(), 407 } 408 409 // Create deployment 410 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 411 412 // One namespace, not including the namespace of the deployment 413 ws.createNamespace(t, "default", nil) 414 415 // No object selector 416 objectSelector := &metav1.LabelSelector{} 417 418 // workload resource does not match 419 found, err := ws.doesObjectMatch(deploy, objectSelector, []string{"apps"}, []string{"v2"}, []string{"deployment"}) 420 assert.NoError(t, err, "unexpected error matching resource") 421 assert.False(t, found, "expected to not find match") 422 } 423 424 // TestNoMatchWildcardVersion tests doesObjectMatch 425 // GIVEN GVK values with wildcard version and no object label selector 426 // WHEN doesObjectMatch is called 427 // THEN a match of false is returned 428 func TestNoMatchWildcardVersion(t *testing.T) { 429 ws := &WorkloadSelector{ 430 KubeClient: fake.NewSimpleClientset(), 431 } 432 433 // Create deployment 434 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 435 436 // Two namespace 437 ws.createNamespace(t, "default", nil) 438 ws.createNamespace(t, "test-ns", nil) 439 440 // No object selector 441 objectSelector := &metav1.LabelSelector{} 442 443 // workload resource does not match 444 found, err := ws.doesObjectMatch(deploy, objectSelector, []string{"foo"}, []string{"*"}, []string{"deployment"}) 445 assert.NoError(t, err, "unexpected error matching resource") 446 assert.False(t, found, "expected to not find match") 447 } 448 449 // TestNoMatchWildcardGroup tests doesObjectMatch 450 // GIVEN GVK values with wildcard group version and no object label selector 451 // WHEN doesObjectMatch is called 452 // THEN a match of false is returned 453 func TestNoMatchWildcardGroup(t *testing.T) { 454 ws := &WorkloadSelector{ 455 KubeClient: fake.NewSimpleClientset(), 456 } 457 458 // Create deployment 459 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 460 461 // Two namespace 462 ws.createNamespace(t, "default", nil) 463 ws.createNamespace(t, "test-ns", nil) 464 465 // No object selector 466 objectSelector := &metav1.LabelSelector{} 467 468 // workload resource does not match 469 found, err := ws.doesObjectMatch(deploy, objectSelector, []string{"*"}, []string{"foo"}, []string{"deployment"}) 470 assert.NoError(t, err, "unexpected error matching resource") 471 assert.False(t, found, "expected to not find match") 472 } 473 474 // TestNoMatchWildcardKind tests doesObjectMatch 475 // GIVEN GVK values with wildcard kind version and no object label selector 476 // WHEN doesObjectMatch is called 477 // THEN a match of false is returned 478 func TestNoMatchWildcardKind(t *testing.T) { 479 ws := &WorkloadSelector{ 480 KubeClient: fake.NewSimpleClientset(), 481 } 482 483 // Create deployment 484 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 485 486 // Two namespace 487 ws.createNamespace(t, "default", nil) 488 ws.createNamespace(t, "test-ns", nil) 489 490 // No object selector 491 objectSelector := &metav1.LabelSelector{} 492 493 // workload resource does not match 494 found, err := ws.doesObjectMatch(deploy, objectSelector, []string{"apps"}, []string{"foo"}, []string{"*"}) 495 assert.NoError(t, err, "unexpected error matching resource") 496 assert.False(t, found, "expected to not find match") 497 } 498 499 // TestMatchLabelsObjectSelectorMatch tests doesObjectMatch 500 // GIVEN an object label selector using a MatchLabel 501 // WHEN doesObjectMatch is called 502 // THEN a match of true is returned 503 func TestMatchLabelsObjectSelectorMatch(t *testing.T) { 504 ws := &WorkloadSelector{ 505 KubeClient: fake.NewSimpleClientset(), 506 } 507 508 labels := map[string]string{ 509 "test-label": "true", 510 } 511 512 // Create deployment 513 deploy := ws.createDeployment(t, "test-ns", "test-deploy", labels) 514 515 // Two namespace 516 ws.createNamespace(t, "default", nil) 517 ws.createNamespace(t, "test-ns", nil) 518 519 // Object selector with match labels 520 objectSelector := &metav1.LabelSelector{ 521 MatchLabels: labels, 522 } 523 524 // workload resource match 525 found, err := ws.doesObjectMatch(deploy, objectSelector, nil, nil, nil) 526 assert.NoError(t, err, "unexpected error matching resource") 527 assert.True(t, found, "expected to find match") 528 } 529 530 // TestMatchExpressionsObjectSelectorMatch tests doesObjectMatch 531 // GIVEN an object label selector using a MatchExpression 532 // WHEN doesObjectMatch is called 533 // THEN a match of true is returned 534 func TestMatchExpressionsObjectSelectorMatch(t *testing.T) { 535 ws := &WorkloadSelector{ 536 KubeClient: fake.NewSimpleClientset(), 537 } 538 539 labels := map[string]string{ 540 "test-label": "true", 541 } 542 543 // Create deployment 544 deploy := ws.createDeployment(t, "test-ns", "test-deploy", labels) 545 546 // Two namespace 547 ws.createNamespace(t, "default", nil) 548 ws.createNamespace(t, "test-ns", nil) 549 550 // Object selector with match expressions 551 objectSelector := &metav1.LabelSelector{ 552 MatchExpressions: []metav1.LabelSelectorRequirement{ 553 { 554 Key: "test-label", 555 Operator: metav1.LabelSelectorOpExists, 556 }, 557 }, 558 } 559 560 // workload resource match 561 found, err := ws.doesObjectMatch(deploy, objectSelector, []string{"*"}, []string{"*"}, []string{"*"}) 562 assert.NoError(t, err, "unexpected error matching resource") 563 assert.True(t, found, "expected to find match") 564 } 565 566 // TestMatchLabelsObjectSelectorNoMatch tests doesObjectMatch 567 // GIVEN an object label selector using a MatchLabel 568 // WHEN doesObjectMatch is called 569 // THEN a match of false is returned 570 func TestMatchLabelsObjectSelectorNoMatch(t *testing.T) { 571 ws := &WorkloadSelector{ 572 KubeClient: fake.NewSimpleClientset(), 573 } 574 575 labels := map[string]string{ 576 "test-label": "true", 577 } 578 579 // Create deployment 580 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 581 582 // Two namespace 583 ws.createNamespace(t, "default", nil) 584 ws.createNamespace(t, "test-ns", nil) 585 586 // Object selector with match labels 587 objectSelector := &metav1.LabelSelector{ 588 MatchLabels: labels, 589 } 590 591 // workload resource match 592 found, err := ws.doesObjectMatch(deploy, objectSelector, nil, nil, nil) 593 assert.NoError(t, err, "unexpected error matching resource") 594 assert.False(t, found, "expected to not find match") 595 } 596 597 // TestMatchExpressionsObjectSelectorNoMatch tests doesObjectMatch 598 // GIVEN an object label selector using a MatchExpression 599 // WHEN doesObjectMatch is called 600 // THEN a match of false is returned 601 func TestMatchExpressionsObjectSelectorNoMatch(t *testing.T) { 602 ws := &WorkloadSelector{ 603 KubeClient: fake.NewSimpleClientset(), 604 } 605 606 // Create deployment 607 deploy := ws.createDeployment(t, "test-ns", "test-deploy", nil) 608 609 // Two namespace 610 ws.createNamespace(t, "default", nil) 611 ws.createNamespace(t, "test-ns", nil) 612 613 // Object selector with match expressions 614 objectSelector := &metav1.LabelSelector{ 615 MatchExpressions: []metav1.LabelSelectorRequirement{ 616 { 617 Key: "test-label", 618 Operator: metav1.LabelSelectorOpExists, 619 }, 620 }, 621 } 622 623 // workload resource did not match 624 found, err := ws.doesObjectMatch(deploy, objectSelector, []string{"*"}, []string{"*"}, []string{"*"}) 625 assert.NoError(t, err, "unexpected error matching resource") 626 assert.False(t, found, "expected to not find match") 627 } 628 629 func (w *WorkloadSelector) createNamespace(t *testing.T, name string, labels map[string]string) { 630 ns := &corev1.Namespace{ 631 ObjectMeta: metav1.ObjectMeta{ 632 Name: name, 633 Labels: labels, 634 }, 635 } 636 _, err := w.KubeClient.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) 637 assert.NoError(t, err, "unexpected error creating namespace") 638 } 639 640 func (w *WorkloadSelector) createDeployment(t *testing.T, namespace string, name string, labels map[string]string) *unstructured.Unstructured { 641 u := newUnstructured("apps/v1", "Deployment", namespace, name) 642 u.SetLabels(labels) 643 resource := schema.GroupVersionResource{ 644 Group: "apps", 645 Version: "v1", 646 Resource: "deployments", 647 } 648 dynamicClient := dynamicfake.NewSimpleDynamicClient(runtime.NewScheme()) 649 uout, err := dynamicClient.Resource(resource).Namespace(namespace).Create(context.TODO(), u, metav1.CreateOptions{}) 650 assert.NoError(t, err, "unexpected error creating deployment") 651 652 return uout 653 } 654 655 func newUnstructured(apiVersion string, kind string, namespace string, name string) *unstructured.Unstructured { 656 return &unstructured.Unstructured{ 657 Object: map[string]interface{}{ 658 "apiVersion": apiVersion, 659 "kind": kind, 660 "metadata": map[string]interface{}{ 661 "namespace": namespace, 662 "name": name, 663 }, 664 }, 665 } 666 }