[apps] Refactor apiserver to use typed objects and fix UnstructuredList GVK (#1679)

## What this PR does

This PR 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

### Release note

```release-note
[apps] Refactor apiserver to use typed objects and fix UnstructuredList GVK handling

This change refactors the apiserver REST handlers to use typed Application objects instead of unstructured.Unstructured, eliminating runtime conversions and simplifying the codebase. Additionally, it fixes an issue where UnstructuredList objects were incorrectly 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 fix includes the upstream fix from kubernetes/kubernetes#135537.
```



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Added a module replacement in dependency configuration to ensure
reproducible builds.

* **Refactor**
* REST internals now use strongly-typed Application and TenantModule
resources and return typed API objects with consistent metadata.

* **Tests**
* Strengthened end-to-end checks for resource kinds and added a
create/delete namespace scenario.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Andrei Kvapil 2025-12-10 13:22:41 +01:00 committed by GitHub
commit e3b96e12be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 91 additions and 214 deletions

3
go.mod
View file

@ -120,3 +120,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=
@ -293,8 +295,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