From 1491535b354618008bd894f419174674bfdb84d1 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 1 Dec 2025 20:00:53 +0100 Subject: [PATCH] [apps] Refactor apiserver to use typed objects and fix UnstructuredList GVK This commit refactors the apiserver REST handlers to use typed objects (appsv1alpha1.Application) instead of unstructured.Unstructured, eliminating the need for runtime conversions and simplifying the codebase. Additionally, it fixes an issue where UnstructuredList objects were using the first registered kind from typeToGVK instead of the kind from the object's field when multiple kinds are registered with the same Go type. This is a more comprehensive fix for the problem addressed in https://github.com/cozystack/cozystack/pull/1630, which was reverted in https://github.com/cozystack/cozystack/pull/1677. The fix includes the upstream fix from kubernetes/kubernetes#135537, which enables short-circuit path for UnstructuredList similar to regular Unstructured objects, using GVK from the object field instead of typeToGVK. Changes: - Refactored rest.go handlers to use typed Application objects - Removed unstructured.Unstructured conversions - Fixed UnstructuredList GVK handling - Updated dependencies in go.mod/go.sum - Added e2e test for OpenAPI validation - Updated Dockerfile Signed-off-by: Andrei Kvapil (cherry picked from commit ca29fc855ab333781211c92fa648ad2efb6a53e1) --- go.mod | 11 +- go.sum | 4 +- hack/e2e-test-openapi.bats | 19 +++ pkg/registry/apps/application/rest.go | 178 +++++-------------------- pkg/registry/core/tenantmodule/rest.go | 101 +++++--------- 5 files changed, 95 insertions(+), 218 deletions(-) diff --git a/go.mod b/go.mod index 041aa96a..a843d2f3 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,15 @@ go 1.23.0 require ( github.com/fluxcd/helm-controller/api v1.1.0 + github.com/go-logr/logr v1.4.2 + github.com/go-logr/zapr v1.3.0 github.com/google/gofuzz v1.2.0 github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 + github.com/prometheus/client_golang v1.19.1 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 + go.uber.org/zap v1.27.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.31.2 k8s.io/apiextensions-apiserver v0.31.2 @@ -44,9 +48,7 @@ require ( github.com/fluxcd/pkg/apis/meta v1.6.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect @@ -74,7 +76,6 @@ require ( github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -94,7 +95,6 @@ require ( go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/net v0.33.0 // indirect @@ -119,3 +119,6 @@ require ( sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) + +// See: issues.k8s.io/135537 +replace k8s.io/apimachinery => github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614 diff --git a/go.sum b/go.sum index a1090f31..a32e6b3b 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614 h1:jH9elECUvhiIs3IMv3oS5k1JgCLVsSK6oU4dmq5gyW8= +github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -291,8 +293,6 @@ k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM= -k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= -k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE= k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= diff --git a/hack/e2e-test-openapi.bats b/hack/e2e-test-openapi.bats index d94cb422..88ed734b 100644 --- a/hack/e2e-test-openapi.bats +++ b/hack/e2e-test-openapi.bats @@ -21,14 +21,33 @@ } @test "Test kinds" { + val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.kind') + if [ "$val" != "TenantList" ]; then + echo "Expected kind to be TenantList, got $val" + exit 1 + fi val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.items[0].kind') if [ "$val" != "Tenant" ]; then echo "Expected kind to be Tenant, got $val" exit 1 fi + val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses | jq -r '.kind') + if [ "$val" != "IngressList" ]; then + echo "Expected kind to be IngressList, got $val" + exit 1 + fi val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses | jq -r '.items[0].kind') if [ "$val" != "Ingress" ]; then echo "Expected kind to be Ingress, got $val" exit 1 fi } + +@test "Create and delete namespace" { + kubectl create ns cozy-test-create-and-delete-namespace --dry-run=client -o yaml | kubectl apply -f - + if ! kubectl delete ns cozy-test-create-and-delete-namespace; then + echo "Failed to delete namespace" + kubectl describe ns cozy-test-create-and-delete-namespace + exit 1 + fi +} diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index 03de7c08..704cd709 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -28,7 +28,6 @@ import ( helmv2 "github.com/fluxcd/helm-controller/api/v2" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" fields "k8s.io/apimachinery/pkg/fields" labels "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -142,17 +141,9 @@ func (r *REST) GetSingularName() string { // Create handles the creation of a new Application by converting it to a HelmRelease func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { // Assert the object is of type Application - us, ok := obj.(*unstructured.Unstructured) + app, ok := obj.(*appsv1alpha1.Application) if !ok { - return nil, fmt.Errorf("expected unstructured.Unstructured object, got %T", obj) - } - - app := &appsv1alpha1.Application{} - - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(us.Object, app); err != nil { - errMsg := fmt.Sprintf("returned unstructured.Unstructured object was not an Application") - klog.Errorf(errMsg) - return nil, fmt.Errorf(errMsg) + return nil, fmt.Errorf("expected *appsv1alpha1.Application object, got %T", obj) } // Convert Application to HelmRelease @@ -186,15 +177,8 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation klog.V(6).Infof("Successfully created and converted HelmRelease %s to Application", helmRelease.GetName()) - // Convert Application to unstructured format - unstructuredApp, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&convertedApp) - if err != nil { - klog.Errorf("Failed to convert Application to unstructured for resource %s: %v", convertedApp.GetName(), err) - return nil, fmt.Errorf("failed to convert Application to unstructured: %v", err) - } - - klog.V(6).Infof("Successfully retrieved and converted resource %s of type %s to unstructured", convertedApp.GetName(), r.gvr.Resource) - return &unstructured.Unstructured{Object: unstructuredApp}, nil + klog.V(6).Infof("Successfully retrieved and converted resource %s of type %s", convertedApp.GetName(), r.gvr.Resource) + return &convertedApp, nil } // Get retrieves an Application by converting the corresponding HelmRelease @@ -238,25 +222,8 @@ func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) return nil, fmt.Errorf("conversion error: %v", err) } - // Explicitly set apiVersion and kind for Application - convertedApp.TypeMeta = metav1.TypeMeta{ - APIVersion: "apps.cozystack.io/v1alpha1", - Kind: r.kindName, - } - - // Convert Application to unstructured format - unstructuredApp, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&convertedApp) - if err != nil { - klog.Errorf("Failed to convert Application to unstructured for resource %s: %v", name, err) - return nil, fmt.Errorf("failed to convert Application to unstructured: %v", err) - } - - // Explicitly set apiVersion and kind in unstructured object - unstructuredApp["apiVersion"] = "apps.cozystack.io/v1alpha1" - unstructuredApp["kind"] = r.kindName - - klog.V(6).Infof("Successfully retrieved and converted resource %s of kind %s to unstructured", name, r.gvr.Resource) - return &unstructured.Unstructured{Object: unstructuredApp}, nil + klog.V(6).Infof("Successfully retrieved and converted resource %s of kind %s", name, r.gvr.Resource) + return &convertedApp, nil } // List retrieves a list of Applications by converting HelmReleases @@ -339,8 +306,8 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption return nil, err } - // Initialize unstructured items array - items := make([]unstructured.Unstructured, 0) + // Initialize Application items array + items := make([]appsv1alpha1.Application, 0, len(hrList.Items)) // Iterate over HelmReleases and convert to Applications for i := range hrList.Items { @@ -387,17 +354,11 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption } } - // Convert Application to unstructured - unstructuredApp, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&app) - if err != nil { - klog.Errorf("Error converting Application %s to unstructured: %v", app.Name, err) - continue - } - items = append(items, unstructured.Unstructured{Object: unstructuredApp}) + items = append(items, app) } - // Explicitly set apiVersion and kind in unstructured object - appList := r.NewList().(*unstructured.UnstructuredList) + // Create ApplicationList with proper kind + appList := r.NewList().(*appsv1alpha1.ApplicationList) appList.SetResourceVersion(hrList.GetResourceVersion()) appList.Items = items @@ -447,16 +408,9 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje } // Assert the new object is of type Application - us, ok := newObj.(*unstructured.Unstructured) + app, ok := newObj.(*appsv1alpha1.Application) if !ok { - errMsg := fmt.Sprintf("expected unstructured.Unstructured object, got %T", newObj) - klog.Errorf(errMsg) - return nil, false, fmt.Errorf(errMsg) - } - app := &appsv1alpha1.Application{} - - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(us.Object, app); err != nil { - errMsg := fmt.Sprintf("returned unstructured.Unstructured object was not an Application") + errMsg := fmt.Sprintf("expected *appsv1alpha1.Application object, got %T", newObj) klog.Errorf(errMsg) return nil, false, fmt.Errorf(errMsg) } @@ -517,24 +471,9 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje klog.V(6).Infof("Successfully updated and converted HelmRelease %s to Application", helmRelease.GetName()) - // Explicitly set apiVersion and kind for Application - convertedApp.TypeMeta = metav1.TypeMeta{ - APIVersion: "apps.cozystack.io/v1alpha1", - Kind: r.kindName, - } + klog.V(6).Infof("Returning updated Application object: %+v", convertedApp) - // Convert Application to unstructured format - unstructuredApp, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&convertedApp) - if err != nil { - klog.Errorf("Failed to convert Application to unstructured for resource %s: %v", convertedApp.GetName(), err) - return nil, false, fmt.Errorf("failed to convert Application to unstructured: %v", err) - } - obj := &unstructured.Unstructured{Object: unstructuredApp} - obj.SetGroupVersionKind(r.gvk) - - klog.V(6).Infof("Returning patched Application object: %+v", unstructuredApp) - - return obj, false, nil + return &convertedApp, false, nil } // Delete removes an Application by deleting the corresponding HelmRelease @@ -728,19 +667,10 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio } } - // Convert Application to unstructured - unstructuredApp, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&app) - if err != nil { - klog.Errorf("Failed to convert Application to unstructured: %v", err) - continue - } - obj := &unstructured.Unstructured{Object: unstructuredApp} - obj.SetGroupVersionKind(r.gvk) - // Create watch event with Application object appEvent := watch.Event{ Type: event.Type, - Object: obj, + Object: &app, } // Send event to custom watcher @@ -766,8 +696,8 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio // Helper function to get HelmRelease name from object func helmReleaseName(obj runtime.Object) string { - if u, ok := obj.(*unstructured.Unstructured); ok { - return u.GetName() + if app, ok := obj.(*appsv1alpha1.Application); ok { + return app.GetName() } return "" } @@ -1059,56 +989,6 @@ func (r *REST) ConvertToTable(ctx context.Context, object runtime.Object, tableO case *appsv1alpha1.Application: table = r.buildTableFromApplication(*obj) table.ListMeta.ResourceVersion = obj.GetResourceVersion() - case *unstructured.UnstructuredList: - apps := make([]appsv1alpha1.Application, 0, len(obj.Items)) - for _, u := range obj.Items { - var a appsv1alpha1.Application - err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &a) - if err != nil { - klog.Errorf("Failed to convert Unstructured to Application: %v", err) - continue - } - apps = append(apps, a) - } - table = r.buildTableFromApplications(apps) - table.ListMeta.ResourceVersion = obj.GetResourceVersion() - case *unstructured.Unstructured: - var apps []appsv1alpha1.Application - for { - var items interface{} - var ok bool - var objects []unstructured.Unstructured - if items, ok = obj.Object["items"]; !ok { - break - } - if objects, ok = items.([]unstructured.Unstructured); !ok { - break - } - apps = make([]appsv1alpha1.Application, 0, len(objects)) - var a appsv1alpha1.Application - for i := range objects { - err := runtime.DefaultUnstructuredConverter.FromUnstructured(objects[i].Object, &a) - if err != nil { - klog.Errorf("Failed to convert Unstructured to Application: %v", err) - continue - } - apps = append(apps, a) - } - break - } - if apps != nil { - table = r.buildTableFromApplications(apps) - table.ListMeta.ResourceVersion = obj.GetResourceVersion() - break - } - var app appsv1alpha1.Application - err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &app) - if err != nil { - klog.Errorf("Failed to convert Unstructured to Application: %v", err) - return nil, fmt.Errorf("failed to convert Unstructured to Application: %v", err) - } - table = r.buildTableFromApplication(app) - table.ListMeta.ResourceVersion = obj.GetResourceVersion() default: resource := schema.GroupResource{} if info, ok := request.RequestInfoFrom(ctx); ok { @@ -1147,10 +1027,11 @@ func (r *REST) buildTableFromApplications(apps []appsv1alpha1.Application) metav } now := time.Now() - for _, app := range apps { + for i := range apps { + app := &apps[i] row := metav1.TableRow{ Cells: []interface{}{app.GetName(), getReadyStatus(app.Status.Conditions), computeAge(app.GetCreationTimestamp().Time, now), getVersion(app.Status.Version)}, - Object: runtime.RawExtension{Object: &app}, + Object: runtime.RawExtension{Object: app}, } table.Rows = append(table.Rows, row) } @@ -1171,9 +1052,10 @@ func (r *REST) buildTableFromApplication(app appsv1alpha1.Application) metav1.Ta } now := time.Now() + a := app row := metav1.TableRow{ Cells: []interface{}{app.GetName(), getReadyStatus(app.Status.Conditions), computeAge(app.GetCreationTimestamp().Time, now), getVersion(app.Status.Version)}, - Object: runtime.RawExtension{Object: &app}, + Object: runtime.RawExtension{Object: &a}, } table.Rows = append(table.Rows, row) @@ -1237,15 +1119,21 @@ func (r *REST) Destroy() { // New creates a new instance of Application func (r *REST) New() runtime.Object { - obj := &unstructured.Unstructured{} - obj.SetGroupVersionKind(r.gvk) + obj := &appsv1alpha1.Application{} + obj.TypeMeta = metav1.TypeMeta{ + APIVersion: r.gvk.GroupVersion().String(), + Kind: r.kindName, + } return obj } // NewList returns an empty list of Application objects func (r *REST) NewList() runtime.Object { - obj := &unstructured.UnstructuredList{} - obj.SetGroupVersionKind(r.gvk.GroupVersion().WithKind(r.kindName + "List")) + obj := &appsv1alpha1.ApplicationList{} + obj.TypeMeta = metav1.TypeMeta{ + APIVersion: r.gvk.GroupVersion().String(), + Kind: r.kindName + "List", + } return obj } diff --git a/pkg/registry/core/tenantmodule/rest.go b/pkg/registry/core/tenantmodule/rest.go index 852a0b64..8ca27dde 100644 --- a/pkg/registry/core/tenantmodule/rest.go +++ b/pkg/registry/core/tenantmodule/rest.go @@ -26,7 +26,6 @@ import ( helmv2 "github.com/fluxcd/helm-controller/api/v2" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" fields "k8s.io/apimachinery/pkg/fields" labels "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -147,25 +146,8 @@ func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) return nil, fmt.Errorf("conversion error: %v", err) } - // Explicitly set apiVersion and kind for TenantModule - convertedModule.TypeMeta = metav1.TypeMeta{ - APIVersion: "core.cozystack.io/v1alpha1", - Kind: r.kindName, - } - - // Convert TenantModule to unstructured format - unstructuredModule, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&convertedModule) - if err != nil { - klog.Errorf("Failed to convert TenantModule to unstructured for resource %s: %v", name, err) - return nil, fmt.Errorf("failed to convert TenantModule to unstructured: %v", err) - } - - // Explicitly set apiVersion and kind in unstructured object - unstructuredModule["apiVersion"] = "core.cozystack.io/v1alpha1" - unstructuredModule["kind"] = r.kindName - - klog.V(6).Infof("Successfully retrieved and converted resource %s of kind %s to unstructured", name, r.gvr.Resource) - return &unstructured.Unstructured{Object: unstructuredModule}, nil + klog.V(6).Infof("Successfully retrieved and converted resource %s of kind %s", name, r.gvr.Resource) + return &convertedModule, nil } // List retrieves a list of TenantModules by converting HelmReleases @@ -245,8 +227,8 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption return nil, err } - // Initialize unstructured items array - items := make([]unstructured.Unstructured, 0) + // Initialize TenantModule items array + items := make([]corev1alpha1.TenantModule, 0, len(hrList.Items)) // Iterate over HelmReleases and convert to TenantModules for i := range hrList.Items { @@ -294,19 +276,15 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption } } - // Convert TenantModule to unstructured - unstructuredModule, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&module) - if err != nil { - klog.Errorf("Error converting TenantModule %s to unstructured: %v", module.Name, err) - continue - } - items = append(items, unstructured.Unstructured{Object: unstructuredModule}) + items = append(items, module) } - // Explicitly set apiVersion and kind in unstructured object - moduleList := &unstructured.UnstructuredList{} - moduleList.SetAPIVersion("core.cozystack.io/v1alpha1") - moduleList.SetKind(r.kindName + "List") + // Create TenantModuleList with proper kind + moduleList := &corev1alpha1.TenantModuleList{} + moduleList.TypeMeta = metav1.TypeMeta{ + APIVersion: "core.cozystack.io/v1alpha1", + Kind: r.kindName + "List", + } moduleList.SetResourceVersion(hrList.GetResourceVersion()) moduleList.Items = items @@ -455,17 +433,10 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio } } - // Convert TenantModule to unstructured - unstructuredModule, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&module) - if err != nil { - klog.Errorf("Failed to convert TenantModule to unstructured: %v", err) - continue - } - // Create watch event with TenantModule object moduleEvent := watch.Event{ Type: event.Type, - Object: &unstructured.Unstructured{Object: unstructuredModule}, + Object: &module, } // Send event to custom watcher @@ -620,27 +591,11 @@ func (r *REST) ConvertToTable(ctx context.Context, object runtime.Object, tableO var table metav1.Table switch obj := object.(type) { - case *unstructured.UnstructuredList: - modules := make([]corev1alpha1.TenantModule, 0, len(obj.Items)) - for _, u := range obj.Items { - var m corev1alpha1.TenantModule - err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &m) - if err != nil { - klog.Errorf("Failed to convert Unstructured to TenantModule: %v", err) - continue - } - modules = append(modules, m) - } - table = r.buildTableFromTenantModules(modules) - table.ListMeta.ResourceVersion = obj.GetResourceVersion() - case *unstructured.Unstructured: - var module corev1alpha1.TenantModule - err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &module) - if err != nil { - klog.Errorf("Failed to convert Unstructured to TenantModule: %v", err) - return nil, fmt.Errorf("failed to convert Unstructured to TenantModule: %v", err) - } - table = r.buildTableFromTenantModule(module) + case *corev1alpha1.TenantModuleList: + table = r.buildTableFromTenantModules(obj.Items) + table.ListMeta.ResourceVersion = obj.ListMeta.ResourceVersion + case *corev1alpha1.TenantModule: + table = r.buildTableFromTenantModule(*obj) table.ListMeta.ResourceVersion = obj.GetResourceVersion() default: resource := schema.GroupResource{} @@ -680,10 +635,11 @@ func (r *REST) buildTableFromTenantModules(modules []corev1alpha1.TenantModule) } now := time.Now() - for _, module := range modules { + for i := range modules { + module := &modules[i] row := metav1.TableRow{ Cells: []interface{}{module.GetName(), getReadyStatus(module.Status.Conditions), computeAge(module.GetCreationTimestamp().Time, now), getVersion(module.Status.Version)}, - Object: runtime.RawExtension{Object: &module}, + Object: runtime.RawExtension{Object: module}, } table.Rows = append(table.Rows, row) } @@ -704,9 +660,10 @@ func (r *REST) buildTableFromTenantModule(module corev1alpha1.TenantModule) meta } now := time.Now() + m := module row := metav1.TableRow{ Cells: []interface{}{module.GetName(), getReadyStatus(module.Status.Conditions), computeAge(module.GetCreationTimestamp().Time, now), getVersion(module.Status.Version)}, - Object: runtime.RawExtension{Object: &module}, + Object: runtime.RawExtension{Object: &m}, } table.Rows = append(table.Rows, row) @@ -751,12 +708,22 @@ func (r *REST) Destroy() { // New creates a new instance of TenantModule func (r *REST) New() runtime.Object { - return &corev1alpha1.TenantModule{} + obj := &corev1alpha1.TenantModule{} + obj.TypeMeta = metav1.TypeMeta{ + APIVersion: r.gvk.GroupVersion().String(), + Kind: r.kindName, + } + return obj } // NewList returns an empty list of TenantModule objects func (r *REST) NewList() runtime.Object { - return &corev1alpha1.TenantModuleList{} + obj := &corev1alpha1.TenantModuleList{} + obj.TypeMeta = metav1.TypeMeta{ + APIVersion: r.gvk.GroupVersion().String(), + Kind: r.kindName + "List", + } + return obj } // Kind returns the resource kind used for API discovery