[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 <kvapss@gmail.com>
(cherry picked from commit ca29fc855a)
This commit is contained in:
Andrei Kvapil 2025-12-01 20:00:53 +01:00 committed by github-actions[bot]
parent 290c6be04b
commit 1491535b35
5 changed files with 95 additions and 218 deletions

11
go.mod
View file

@ -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

4
go.sum
View file

@ -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=

View file

@ -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
}

View file

@ -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 "<unknown>"
}
@ -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
}

View file

@ -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