@@ -20,6 +20,8 @@ import (
2020 "bytes"
2121 "context"
2222 "fmt"
23+ "os"
24+ "strings"
2325 "time"
2426
2527 "github.com/onsi/ginkgo/v2"
@@ -30,12 +32,15 @@ import (
3032 apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3133 apierrors "k8s.io/apimachinery/pkg/api/errors"
3234 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3336 "k8s.io/apimachinery/pkg/runtime"
37+ "k8s.io/apimachinery/pkg/runtime/serializer"
3438 "k8s.io/apimachinery/pkg/types"
3539 "k8s.io/client-go/kubernetes"
3640 "k8s.io/client-go/rest"
3741 "k8s.io/client-go/tools/remotecommand"
3842 "sigs.k8s.io/controller-runtime/pkg/client"
43+ "sigs.k8s.io/controller-runtime/pkg/client/config"
3944
4045 v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
4146 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
@@ -372,4 +377,128 @@ func EventuallyExists(testConfig *TestConfig, getResource func() error) {
372377 }, testConfig .ExistsTimeout , testConfig .Interval ).Should (gomega .Succeed ())
373378}
374379
380+ // CreateObjsFromYaml creates K8S objects from yaml and waits for them to be instantiated
381+ func CreateObjsFromYaml (testConfig * TestConfig , docs []string ) []string {
382+ objNames := []string {}
383+
384+ // For each doc, decode and create
385+ decoder := serializer .NewCodecFactory (testConfig .Scheme ).UniversalDeserializer ()
386+ for _ , doc := range docs {
387+ trimmed := strings .TrimSpace (doc )
388+ if trimmed == "" {
389+ continue
390+ }
391+ // Decode into a runtime.Object
392+ obj , gvk , decodeErr := decoder .Decode ([]byte (trimmed ), nil , nil )
393+ gomega .Expect (decodeErr ).NotTo (gomega .HaveOccurred (),
394+ "Failed to decode YAML document to a Kubernetes object" )
395+
396+ ginkgo .By (fmt .Sprintf ("Decoded GVK: %s" , gvk ))
397+
398+ unstrObj , ok := obj .(* unstructured.Unstructured )
399+ if ! ok {
400+ // Fallback if it's a typed object
401+ unstrObj = & unstructured.Unstructured {}
402+ // Convert typed to unstructured
403+ err := testConfig .Scheme .Convert (obj , unstrObj , nil )
404+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
405+ }
406+
407+ unstrObj .SetNamespace (testConfig .NsName )
408+ kind := unstrObj .GetKind ()
409+ name := unstrObj .GetName ()
410+ objNames = append (objNames , kind + "/" + name )
411+
412+ // Create the object
413+ err := testConfig .K8sClient .Create (testConfig .Context , unstrObj , & client.CreateOptions {})
414+ gomega .Expect (err ).NotTo (gomega .HaveOccurred (),
415+ "Failed to create object from YAML" )
416+
417+ // Wait for the created object to exist.
418+ clientObj := getClientObject (kind )
419+ EventuallyExists (testConfig , func () error {
420+ return testConfig .K8sClient .Get (testConfig .Context ,
421+ types.NamespacedName {Namespace : testConfig .NsName , Name : name }, clientObj )
422+ })
423+
424+ switch kind {
425+ case "CustomResourceDefinition" :
426+ // Wait for the CRD to be established.
427+ CRDEstablished (testConfig , clientObj .(* apiextv1.CustomResourceDefinition ))
428+ case "Deployment" :
429+ // Wait for the deployment to be available.
430+ DeploymentAvailable (testConfig , clientObj .(* appsv1.Deployment ))
431+ case "Pod" :
432+ // Wait for the pod to be ready.
433+ PodReady (testConfig , clientObj .(* corev1.Pod ))
434+ }
435+ }
436+ return objNames
437+ }
438+
439+ func DeleteObjects (testConfig * TestConfig , kindAndNames []string ) {
440+ for _ , kindAndName := range kindAndNames {
441+ split := strings .Split (kindAndName , "/" )
442+ clientObj := getClientObject (split [0 ])
443+ err := testConfig .K8sClient .Get (testConfig .Context ,
444+ types.NamespacedName {Namespace : testConfig .NsName , Name : split [1 ]}, clientObj )
445+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
446+ err = testConfig .K8sClient .Delete (testConfig .Context , clientObj )
447+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
448+ gomega .Eventually (func () bool {
449+ clientObj := getClientObject (split [0 ])
450+ err := testConfig .K8sClient .Get (testConfig .Context ,
451+ types.NamespacedName {Namespace : testConfig .NsName , Name : split [1 ]}, clientObj )
452+ return apierrors .IsNotFound (err )
453+ }, testConfig .ExistsTimeout , testConfig .Interval ).Should (gomega .BeTrue ())
454+ }
455+ }
456+
457+ // applyYAMLFile reads a file containing YAML (possibly multiple docs)
458+ // and applies each object to the cluster.
459+ func ApplyYAMLFile (testConfig * TestConfig , filePath string ) {
460+ // Create the resources from the manifest file
461+ CreateObjsFromYaml (testConfig , ReadYaml (filePath ))
462+ }
463+
464+ // ReadYaml is a helper function to read in K8S YAML files and split by the --- separator
465+ func ReadYaml (filePath string ) []string {
466+ ginkgo .By ("Reading YAML file: " + filePath )
467+ yamlBytes , err := os .ReadFile (filePath )
468+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
469+
470+ // Split multiple docs, if needed
471+ return strings .Split (string (yamlBytes ), "\n ---" )
472+ }
473+
474+ func getClientObject (kind string ) client.Object {
475+ switch strings .ToLower (kind ) {
476+ case "clusterrole" :
477+ return & rbacv1.ClusterRole {}
478+ case "clusterrolebinding" :
479+ return & rbacv1.ClusterRoleBinding {}
480+ case "configmap" :
481+ return & corev1.ConfigMap {}
482+ case "customresourcedefinition" :
483+ return & apiextv1.CustomResourceDefinition {}
484+ case "deployment" :
485+ return & appsv1.Deployment {}
486+ case "inferencepool" :
487+ return & v1.InferencePool {}
488+ case "pod" :
489+ return & corev1.Pod {}
490+ case "role" :
491+ return & rbacv1.Role {}
492+ case "rolebinding" :
493+ return & rbacv1.RoleBinding {}
494+ case "secret" :
495+ return & corev1.Secret {}
496+ case "service" :
497+ return & corev1.Service {}
498+ case "serviceaccount" :
499+ return & corev1.ServiceAccount {}
500+ default :
501+ ginkgo .Fail ("unsupported K8S kind " + kind , 1 )
502+ return nil
503+ }
375504}
0 commit comments