[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:
commit
e3b96e12be
5 changed files with 91 additions and 214 deletions
3
go.mod
3
go.mod
|
|
@ -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
4
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=
|
||||
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue