k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/apiserver/discovery/framework.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package discovery 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "reflect" 24 "strings" 25 "testing" 26 "time" 27 28 apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 29 "k8s.io/apimachinery/pkg/api/errors" 30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/apimachinery/pkg/util/sets" 34 "k8s.io/apimachinery/pkg/util/wait" 35 "k8s.io/client-go/dynamic" 36 "k8s.io/client-go/kubernetes" 37 aggregator "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset" 38 39 apidiscoveryv2 "k8s.io/api/apidiscovery/v2" 40 apidiscoveryv2beta1 "k8s.io/api/apidiscovery/v2beta1" 41 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 42 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 43 apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" 44 ) 45 46 const acceptV1JSON = "application/json" 47 const acceptV2Beta1JSON = "application/json;g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList" 48 const acceptV2JSON = "application/json;g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList" 49 50 const maxTimeout = 10 * time.Second 51 52 type testClient interface { 53 kubernetes.Interface 54 aggregator.Interface 55 apiextensions.Interface 56 dynamic.Interface 57 } 58 59 // declarative framework for discovery integration tests 60 // each test has metadata and a list of actions which each must pass for the 61 // test to pass 62 type testCase struct { 63 Name string 64 Actions []testAction 65 } 66 67 // interface defining a function that does something with the integration test 68 // api server and returns an error. the test fails if the error is non nil 69 type testAction interface { 70 Do(ctx context.Context, client testClient) error 71 } 72 73 type cleaningAction interface { 74 testAction 75 Cleanup(ctx context.Context, client testClient) error 76 } 77 78 // apply an apiservice to the cluster 79 type applyAPIService apiregistrationv1.APIServiceSpec 80 81 type applyCRD apiextensionsv1.CustomResourceDefinitionSpec 82 83 type deleteObject struct { 84 metav1.GroupVersionResource 85 Namespace string 86 Name string 87 } 88 89 // Wait for groupversions to appear in v1 discovery 90 type waitForGroupVersionsV1 []metav1.GroupVersion 91 92 // Wait for groupversions to disappear from v2 discovery 93 type waitForAbsentGroupVersionsV1 []metav1.GroupVersion 94 95 // Wait for groupversions to appear in v2 discovery 96 type waitForGroupVersionsV2 []metav1.GroupVersion 97 98 // Wait for groupversions to appear in v2beta1 discovery 99 type waitForGroupVersionsV2Beta1 []metav1.GroupVersion 100 101 // Wait for groupversions to disappear from v2 discovery 102 type waitForAbsentGroupVersionsV2 []metav1.GroupVersion 103 104 // Wait for groupversions to disappear from v2beta1 discovery 105 type waitForAbsentGroupVersionsV2Beta1 []metav1.GroupVersion 106 107 type waitForStaleGroupVersionsV2 []metav1.GroupVersion 108 type waitForFreshGroupVersionsV2 []metav1.GroupVersion 109 110 type waitForResourcesV1 []metav1.GroupVersionResource 111 type waitForResourcesAbsentV1 []metav1.GroupVersionResource 112 113 type waitForResourcesV2 []metav1.GroupVersionResource 114 type waitForResourcesAbsentV2 []metav1.GroupVersionResource 115 116 // Assert something about the current state of v2 discovery 117 type inlineAction func(ctx context.Context, client testClient) error 118 119 func (a applyAPIService) Do(ctx context.Context, client testClient) error { 120 // using dynamic client since the typed client does not support `Apply` 121 // operation? 122 obj := &apiregistrationv1.APIService{ 123 ObjectMeta: metav1.ObjectMeta{ 124 Name: a.Version + "." + a.Group, 125 }, 126 Spec: apiregistrationv1.APIServiceSpec(a), 127 } 128 129 unstructuredContent, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) 130 if err != nil { 131 return err 132 } 133 134 unstructedObject := &unstructured.Unstructured{} 135 unstructedObject.SetUnstructuredContent(unstructuredContent) 136 unstructedObject.SetGroupVersionKind(apiregistrationv1.SchemeGroupVersion.WithKind("APIService")) 137 138 _, err = client. 139 Resource(apiregistrationv1.SchemeGroupVersion.WithResource("apiservices")). 140 Apply(ctx, obj.Name, unstructedObject, metav1.ApplyOptions{ 141 FieldManager: "test-manager", 142 }) 143 144 return err 145 } 146 147 func (a applyAPIService) Cleanup(ctx context.Context, client testClient) error { 148 name := a.Version + "." + a.Group 149 err := client.ApiregistrationV1().APIServices().Delete(ctx, name, metav1.DeleteOptions{}) 150 151 if !errors.IsNotFound(err) { 152 return err 153 } 154 155 err = wait.PollUntilContextTimeout( 156 ctx, 157 250*time.Millisecond, 158 maxTimeout, 159 true, 160 func(ctx context.Context) (done bool, err error) { 161 _, err = client.ApiregistrationV1().APIServices().Get(ctx, name, metav1.GetOptions{}) 162 if err == nil { 163 return false, nil 164 } 165 166 if !errors.IsNotFound(err) { 167 return false, err 168 } 169 return true, nil 170 }, 171 ) 172 173 if err != nil { 174 return fmt.Errorf("error waiting for APIService %v to clean up: %w", name, err) 175 } 176 177 return nil 178 } 179 180 func (a applyCRD) Do(ctx context.Context, client testClient) error { 181 // using dynamic client since the typed client does not support `Apply` 182 // operation? 183 name := a.Names.Plural + "." + a.Group 184 obj := &apiextensionsv1.CustomResourceDefinition{ 185 ObjectMeta: metav1.ObjectMeta{ 186 Name: name, 187 }, 188 Spec: apiextensionsv1.CustomResourceDefinitionSpec(a), 189 } 190 191 if strings.HasSuffix(obj.Name, ".k8s.io") { 192 if obj.Annotations == nil { 193 obj.Annotations = map[string]string{} 194 } 195 obj.Annotations["api-approved.kubernetes.io"] = "https://github.com/kubernetes/kubernetes/fake" 196 } 197 198 unstructuredContent, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) 199 if err != nil { 200 return err 201 } 202 203 unstructedObject := &unstructured.Unstructured{} 204 unstructedObject.SetUnstructuredContent(unstructuredContent) 205 unstructedObject.SetGroupVersionKind(apiextensionsv1.SchemeGroupVersion.WithKind("CustomResourceDefinition")) 206 207 _, err = client. 208 Resource(apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions")). 209 Apply(ctx, obj.Name, unstructedObject, metav1.ApplyOptions{ 210 FieldManager: "test-manager", 211 }) 212 213 return err 214 } 215 216 func (a applyCRD) Cleanup(ctx context.Context, client testClient) error { 217 name := a.Names.Plural + "." + a.Group 218 err := client.ApiextensionsV1().CustomResourceDefinitions().Delete(ctx, name, metav1.DeleteOptions{}) 219 220 if !errors.IsNotFound(err) { 221 return err 222 } 223 224 err = wait.PollUntilContextTimeout( 225 ctx, 226 250*time.Millisecond, 227 maxTimeout, 228 true, 229 func(ctx context.Context) (done bool, err error) { 230 _, err = client.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, name, metav1.GetOptions{}) 231 if err == nil { 232 return false, nil 233 } 234 235 if !errors.IsNotFound(err) { 236 return false, err 237 } 238 return true, nil 239 }, 240 ) 241 242 if err != nil { 243 return fmt.Errorf("error waiting for CRD %v to clean up: %w", name, err) 244 } 245 246 return nil 247 } 248 249 func (d deleteObject) Do(ctx context.Context, client testClient) error { 250 if d.Namespace == "" { 251 return client.Resource(schema.GroupVersionResource(d.GroupVersionResource)). 252 Delete(ctx, d.Name, metav1.DeleteOptions{}) 253 } else { 254 return client.Resource(schema.GroupVersionResource(d.GroupVersionResource)). 255 Namespace(d.Namespace). 256 Delete(ctx, d.Name, metav1.DeleteOptions{}) 257 } 258 } 259 260 func (w waitForStaleGroupVersionsV2) Do(ctx context.Context, client testClient) error { 261 err := WaitForResultWithCondition(ctx, client, func(result apidiscoveryv2.APIGroupDiscoveryList) bool { 262 for _, gv := range w { 263 if info := FindGroupVersionV2(result, gv); info == nil || info.Freshness != apidiscoveryv2.DiscoveryFreshnessStale { 264 return false 265 } 266 } 267 268 return true 269 }) 270 271 if err != nil { 272 return fmt.Errorf("waiting for stale groupversions v2 (%v): %w", w, err) 273 } 274 return nil 275 } 276 277 func (w waitForFreshGroupVersionsV2) Do(ctx context.Context, client testClient) error { 278 err := WaitForResultWithCondition(ctx, client, func(result apidiscoveryv2.APIGroupDiscoveryList) bool { 279 for _, gv := range w { 280 if info := FindGroupVersionV2(result, gv); info == nil || info.Freshness != apidiscoveryv2.DiscoveryFreshnessCurrent { 281 return false 282 } 283 } 284 285 return true 286 }) 287 288 if err != nil { 289 return fmt.Errorf("waiting for fresh groupversions v2 (%v): %w", w, err) 290 } 291 return nil 292 } 293 294 func (w waitForGroupVersionsV2) Do(ctx context.Context, client testClient) error { 295 err := WaitForResultWithCondition(ctx, client, func(result apidiscoveryv2.APIGroupDiscoveryList) bool { 296 for _, gv := range w { 297 if FindGroupVersionV2(result, gv) == nil { 298 return false 299 } 300 } 301 302 return true 303 }) 304 305 if err != nil { 306 return fmt.Errorf("waiting for groupversions v2 (%v): %w", w, err) 307 } 308 return nil 309 } 310 311 func (w waitForGroupVersionsV2Beta1) Do(ctx context.Context, client testClient) error { 312 err := WaitForV2Beta1ResultWithCondition(ctx, client, func(result apidiscoveryv2beta1.APIGroupDiscoveryList) bool { 313 for _, gv := range w { 314 if FindGroupVersionV2Beta1(result, gv) == nil { 315 return false 316 } 317 } 318 319 return true 320 }) 321 322 if err != nil { 323 return fmt.Errorf("waiting for groupversions v2 (%v): %w", w, err) 324 } 325 return nil 326 } 327 328 func (w waitForAbsentGroupVersionsV2) Do(ctx context.Context, client testClient) error { 329 err := WaitForResultWithCondition(ctx, client, func(result apidiscoveryv2.APIGroupDiscoveryList) bool { 330 for _, gv := range w { 331 if FindGroupVersionV2(result, gv) != nil { 332 return false 333 } 334 } 335 336 return true 337 }) 338 339 if err != nil { 340 return fmt.Errorf("waiting for absent groupversions v2 (%v): %w", w, err) 341 } 342 return nil 343 } 344 345 func (w waitForAbsentGroupVersionsV2Beta1) Do(ctx context.Context, client testClient) error { 346 err := WaitForV2Beta1ResultWithCondition(ctx, client, func(result apidiscoveryv2beta1.APIGroupDiscoveryList) bool { 347 for _, gv := range w { 348 if FindGroupVersionV2Beta1(result, gv) != nil { 349 return false 350 } 351 } 352 353 return true 354 }) 355 356 if err != nil { 357 return fmt.Errorf("waiting for absent groupversions v2 (%v): %w", w, err) 358 } 359 return nil 360 } 361 362 func (w waitForGroupVersionsV1) Do(ctx context.Context, client testClient) error { 363 err := WaitForV1GroupsWithCondition(ctx, client, func(result metav1.APIGroupList) bool { 364 for _, gv := range w { 365 if !FindGroupVersionV1(result, gv) { 366 return false 367 } 368 } 369 370 return true 371 }) 372 373 if err != nil { 374 return fmt.Errorf("waiting for groupversions v1 (%v): %w", w, err) 375 } 376 return nil 377 } 378 379 func (w waitForAbsentGroupVersionsV1) Do(ctx context.Context, client testClient) error { 380 err := WaitForV1GroupsWithCondition(ctx, client, func(result metav1.APIGroupList) bool { 381 for _, gv := range w { 382 if FindGroupVersionV1(result, gv) { 383 return false 384 } 385 } 386 387 return true 388 }) 389 390 if err != nil { 391 return fmt.Errorf("waiting for absent groupversions v1 (%v): %w", w, err) 392 } 393 return nil 394 } 395 396 func (w waitForResourcesV1) Do(ctx context.Context, client testClient) error { 397 requiredResources := map[metav1.GroupVersion][]string{} 398 399 for _, gvr := range w { 400 gv := metav1.GroupVersion{Group: gvr.Group, Version: gvr.Version} 401 if existing, ok := requiredResources[gv]; ok { 402 requiredResources[gv] = append(existing, gvr.Resource) 403 } else { 404 requiredResources[gv] = []string{gvr.Resource} 405 } 406 } 407 408 for gv, resourceNames := range requiredResources { 409 err := WaitForV1ResourcesWithCondition(ctx, client, gv, func(result metav1.APIResourceList) bool { 410 for _, name := range resourceNames { 411 found := false 412 413 for _, resultResource := range result.APIResources { 414 if resultResource.Name == name { 415 found = true 416 break 417 } 418 } 419 420 if !found { 421 return false 422 } 423 } 424 425 return true 426 }) 427 428 if err != nil { 429 if errors.IsNotFound(err) { 430 return nil 431 } 432 return fmt.Errorf("waiting for resources v1 (%v): %w", w, err) 433 } 434 } 435 436 return nil 437 } 438 439 func (w waitForResourcesAbsentV1) Do(ctx context.Context, client testClient) error { 440 requiredResources := map[metav1.GroupVersion][]string{} 441 442 for _, gvr := range w { 443 gv := metav1.GroupVersion{Group: gvr.Group, Version: gvr.Version} 444 if existing, ok := requiredResources[gv]; ok { 445 requiredResources[gv] = append(existing, gvr.Resource) 446 } else { 447 requiredResources[gv] = []string{gvr.Resource} 448 } 449 } 450 451 for gv, resourceNames := range requiredResources { 452 err := WaitForV1ResourcesWithCondition(ctx, client, gv, func(result metav1.APIResourceList) bool { 453 for _, name := range resourceNames { 454 for _, resultResource := range result.APIResources { 455 if resultResource.Name == name { 456 return false 457 } 458 } 459 } 460 461 return true 462 }) 463 464 if err != nil { 465 if errors.IsNotFound(err) { 466 return nil 467 } 468 return fmt.Errorf("waiting for absent resources v1 (%v): %w", w, err) 469 } 470 } 471 472 return nil 473 } 474 475 func (w waitForResourcesV2) Do(ctx context.Context, client testClient) error { 476 err := WaitForResultWithCondition(ctx, client, func(result apidiscoveryv2.APIGroupDiscoveryList) bool { 477 for _, gvr := range w { 478 if info := FindGroupVersionV2(result, metav1.GroupVersion{Group: gvr.Group, Version: gvr.Version}); info == nil { 479 return false 480 } else { 481 found := false 482 for _, resultResoure := range info.Resources { 483 if resultResoure.Resource == gvr.Resource { 484 found = true 485 break 486 } 487 } 488 489 if !found { 490 return false 491 } 492 } 493 } 494 495 return true 496 }) 497 498 if err != nil { 499 return fmt.Errorf("waiting for resources v2 (%v): %w", w, err) 500 } 501 return nil 502 } 503 504 func (w waitForResourcesAbsentV2) Do(ctx context.Context, client testClient) error { 505 err := WaitForResultWithCondition(ctx, client, func(result apidiscoveryv2.APIGroupDiscoveryList) bool { 506 for _, gvr := range w { 507 if info := FindGroupVersionV2(result, metav1.GroupVersion{Group: gvr.Group, Version: gvr.Version}); info == nil { 508 return false 509 } else { 510 for _, resultResoure := range info.Resources { 511 if resultResoure.Resource == gvr.Resource { 512 return false 513 } 514 } 515 } 516 } 517 518 return true 519 }) 520 521 if err != nil { 522 return fmt.Errorf("waiting for absent resources v2 (%v): %w", w, err) 523 } 524 return nil 525 } 526 527 func (i inlineAction) Do(ctx context.Context, client testClient) error { 528 return i(ctx, client) 529 } 530 531 func FetchV2Discovery(ctx context.Context, client testClient) (apidiscoveryv2.APIGroupDiscoveryList, error) { 532 result, err := client. 533 Discovery(). 534 RESTClient(). 535 Get(). 536 AbsPath("/apis"). 537 SetHeader("Accept", acceptV2JSON). 538 Do(ctx). 539 Raw() 540 541 if err != nil { 542 return apidiscoveryv2.APIGroupDiscoveryList{}, fmt.Errorf("failed to fetch v2 discovery: %w", err) 543 } 544 545 groupList := apidiscoveryv2.APIGroupDiscoveryList{} 546 err = json.Unmarshal(result, &groupList) 547 if err != nil { 548 return apidiscoveryv2.APIGroupDiscoveryList{}, fmt.Errorf("failed to parse v2 discovery: %w", err) 549 } 550 551 return groupList, nil 552 } 553 554 func FetchV2Beta1Discovery(ctx context.Context, client testClient) (apidiscoveryv2beta1.APIGroupDiscoveryList, error) { 555 result, err := client. 556 Discovery(). 557 RESTClient(). 558 Get(). 559 AbsPath("/apis"). 560 SetHeader("Accept", acceptV2Beta1JSON). 561 Do(ctx). 562 Raw() 563 564 if err != nil { 565 return apidiscoveryv2beta1.APIGroupDiscoveryList{}, fmt.Errorf("failed to fetch v2 discovery: %w", err) 566 } 567 568 groupList := apidiscoveryv2beta1.APIGroupDiscoveryList{} 569 err = json.Unmarshal(result, &groupList) 570 if err != nil { 571 return apidiscoveryv2beta1.APIGroupDiscoveryList{}, fmt.Errorf("failed to parse v2 discovery: %w", err) 572 } 573 574 return groupList, nil 575 } 576 577 func FetchV1DiscoveryGroups(ctx context.Context, client testClient) (metav1.APIGroupList, error) { 578 return FetchV1DiscoveryGroupsAtPath(ctx, client, "/apis") 579 } 580 581 func FetchV1DiscoveryLegacyGroups(ctx context.Context, client testClient) (metav1.APIGroupList, error) { 582 return FetchV1DiscoveryGroupsAtPath(ctx, client, "/api") 583 } 584 585 func FetchV1DiscoveryGroupsAtPath(ctx context.Context, client testClient, path string) (metav1.APIGroupList, error) { 586 result, err := client. 587 Discovery(). 588 RESTClient(). 589 Get(). 590 AbsPath(path). 591 SetHeader("Accept", acceptV1JSON). 592 Do(ctx). 593 Raw() 594 595 if err != nil { 596 return metav1.APIGroupList{}, fmt.Errorf("failed to fetch v1 discovery at %v: %w", path, err) 597 } 598 599 groupList := metav1.APIGroupList{} 600 err = json.Unmarshal(result, &groupList) 601 if err != nil { 602 return metav1.APIGroupList{}, fmt.Errorf("failed to parse v1 discovery at %v: %w", path, err) 603 } 604 605 return groupList, nil 606 } 607 608 func FetchV1DiscoveryResource(ctx context.Context, client testClient, gv metav1.GroupVersion) (metav1.APIResourceList, error) { 609 result, err := client. 610 Discovery(). 611 RESTClient(). 612 Get(). 613 AbsPath("/apis/"+gv.Group+"/"+gv.Version). 614 SetHeader("Accept", acceptV1JSON). 615 Do(ctx). 616 Raw() 617 618 if err != nil { 619 return metav1.APIResourceList{}, err 620 } 621 622 groupList := metav1.APIResourceList{} 623 err = json.Unmarshal(result, &groupList) 624 if err != nil { 625 return metav1.APIResourceList{}, err 626 } 627 628 return groupList, nil 629 } 630 631 func WaitForGroupsAbsent(ctx context.Context, client testClient, groups ...string) error { 632 return WaitForResultWithCondition(ctx, client, func(groupList apidiscoveryv2.APIGroupDiscoveryList) bool { 633 for _, searchGroup := range groups { 634 for _, docGroup := range groupList.Items { 635 if docGroup.Name == searchGroup { 636 return false 637 } 638 } 639 } 640 return true 641 }) 642 643 } 644 645 func WaitForRootPaths(t *testing.T, ctx context.Context, client testClient, requirePaths, forbidPaths sets.Set[string]) error { 646 return wait.PollUntilContextTimeout(ctx, 250*time.Millisecond, maxTimeout, true, func(ctx context.Context) (done bool, err error) { 647 statusContent, err := client.Discovery().RESTClient().Get().AbsPath("/").SetHeader("Accept", "application/json").DoRaw(ctx) 648 if err != nil { 649 return false, err 650 } 651 rootPaths := metav1.RootPaths{} 652 if err := json.Unmarshal(statusContent, &rootPaths); err != nil { 653 return false, err 654 } 655 paths := sets.New(rootPaths.Paths...) 656 if missing := requirePaths.Difference(paths); len(missing) > 0 { 657 t.Logf("missing required root paths %v", sets.List(missing)) 658 return false, nil 659 } 660 if present := forbidPaths.Intersection(paths); len(present) > 0 { 661 t.Logf("present forbidden root paths %v", sets.List(present)) 662 return false, nil 663 } 664 return true, nil 665 }) 666 } 667 668 func WaitForGroups(ctx context.Context, client testClient, groups ...apidiscoveryv2.APIGroupDiscovery) error { 669 return WaitForResultWithCondition(ctx, client, func(groupList apidiscoveryv2.APIGroupDiscoveryList) bool { 670 for _, searchGroup := range groups { 671 found := false 672 for _, docGroup := range groupList.Items { 673 if reflect.DeepEqual(searchGroup, docGroup) { 674 found = true 675 break 676 } 677 } 678 if !found { 679 return false 680 } 681 } 682 return true 683 }) 684 } 685 686 func WaitForResultWithCondition(ctx context.Context, client testClient, condition func(result apidiscoveryv2.APIGroupDiscoveryList) bool) error { 687 // Keep repeatedly fetching document from aggregator. 688 // Check to see if it contains our service within a reasonable amount of time 689 return wait.PollUntilContextTimeout( 690 ctx, 691 250*time.Millisecond, 692 maxTimeout, 693 true, 694 func(ctx context.Context) (done bool, err error) { 695 groupList, err := FetchV2Discovery(ctx, client) 696 if err != nil { 697 return false, err 698 } 699 700 if condition(groupList) { 701 return true, nil 702 } 703 704 return false, nil 705 }) 706 } 707 708 func WaitForV2Beta1ResultWithCondition(ctx context.Context, client testClient, condition func(result apidiscoveryv2beta1.APIGroupDiscoveryList) bool) error { 709 // Keep repeatedly fetching document from aggregator. 710 // Check to see if it contains our service within a reasonable amount of time 711 return wait.PollUntilContextTimeout( 712 ctx, 713 250*time.Millisecond, 714 maxTimeout, 715 true, 716 func(ctx context.Context) (done bool, err error) { 717 groupList, err := FetchV2Beta1Discovery(ctx, client) 718 if err != nil { 719 return false, err 720 } 721 722 if condition(groupList) { 723 return true, nil 724 } 725 726 return false, nil 727 }) 728 } 729 730 func WaitForV1GroupsWithCondition(ctx context.Context, client testClient, condition func(result metav1.APIGroupList) bool) error { 731 // Keep repeatedly fetching document from aggregator. 732 // Check to see if it contains our service within a reasonable amount of time 733 return wait.PollUntilContextTimeout( 734 ctx, 735 250*time.Millisecond, 736 maxTimeout, 737 true, 738 func(ctx context.Context) (done bool, err error) { 739 groupList, err := FetchV1DiscoveryGroups(ctx, client) 740 741 if err != nil { 742 return false, err 743 } 744 745 if condition(groupList) { 746 return true, nil 747 } 748 749 return false, nil 750 }) 751 } 752 753 func WaitForV1ResourcesWithCondition(ctx context.Context, client testClient, gv metav1.GroupVersion, condition func(result metav1.APIResourceList) bool) error { 754 // Keep repeatedly fetching document from aggregator. 755 // Check to see if it contains our service within a reasonable amount of time 756 return wait.PollUntilContextTimeout( 757 ctx, 758 250*time.Millisecond, 759 maxTimeout, 760 true, 761 func(ctx context.Context) (done bool, err error) { 762 resourceList, err := FetchV1DiscoveryResource(ctx, client, gv) 763 764 if err != nil { 765 return false, err 766 } 767 768 if condition(resourceList) { 769 return true, nil 770 } 771 772 return false, nil 773 }) 774 } 775 776 func FindGroupVersionV1(discovery metav1.APIGroupList, gv metav1.GroupVersion) bool { 777 for _, documentGroup := range discovery.Groups { 778 if documentGroup.Name != gv.Group { 779 continue 780 } 781 782 for _, documentVersion := range documentGroup.Versions { 783 if documentVersion.Version == gv.Version { 784 return true 785 } 786 } 787 } 788 789 return false 790 } 791 792 func FindGroupVersionV2(discovery apidiscoveryv2.APIGroupDiscoveryList, gv metav1.GroupVersion) *apidiscoveryv2.APIVersionDiscovery { 793 for _, documentGroup := range discovery.Items { 794 if documentGroup.Name != gv.Group { 795 continue 796 } 797 798 for _, documentVersion := range documentGroup.Versions { 799 if documentVersion.Version == gv.Version { 800 return &documentVersion 801 } 802 } 803 } 804 805 return nil 806 } 807 808 func FindGroupVersionV2Beta1(discovery apidiscoveryv2beta1.APIGroupDiscoveryList, gv metav1.GroupVersion) *apidiscoveryv2beta1.APIVersionDiscovery { 809 for _, documentGroup := range discovery.Items { 810 if documentGroup.Name != gv.Group { 811 continue 812 } 813 814 for _, documentVersion := range documentGroup.Versions { 815 if documentVersion.Version == gv.Version { 816 return &documentVersion 817 } 818 } 819 } 820 821 return nil 822 }