Skip to content

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

Merged
Andrei Kvapil (kvaps) merged 2 commits into
mainfrom
fix-namespace-removal
Dec 10, 2025
Merged

Andrei Kvapil (kvaps) merged 2 commits into
mainfrom
fix-namespace-removal

Conversation

@kvaps

@kvaps Andrei Kvapil (kvaps) commented Dec 1, 2025 •

Copy link
Copy Markdown
Member

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
#1630, which was reverted in
#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

[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.

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.

✏️ Tip: You can customize this high-level summary in your review settings.

@dosubot dosubot Bot added the size/L This PR changes 100-499 lines, ignoring generated files label Dec 1, 2025
@coderabbitai

coderabbitai Bot commented Dec 1, 2025 •

Copy link
Copy Markdown
Contributor

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Replaces unstructured REST handling with strongly-typed Application and TenantModule types; adds a go.mod replace directive for k8s.io/apimachinery; and expands e2e OpenAPI tests with top-level kind checks and a namespace create/delete test.

Changes

Cohort / File(s) Summary
Go module replace
go.mod
Adds replace k8s.io/apimachinery => github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614 with a comment referencing issues.k8s.io/135537.
E2E OpenAPI Tests
hack/e2e-test-openapi.bats
Adds top-level Kind assertions for tenants (TenantList) and ingresses (IngressList), retains item-kind checks, and adds a "Create and delete namespace" test (kubectl apply --dry-run then delete with diagnostics on failure).
Application REST Handler
pkg/registry/apps/application/rest.go
Replaces unstructured-based flows with typed appsv1alpha1.Application and ApplicationList across Create/Get/List/Update/Watch; ensures returned objects have explicit TypeMeta; removes unstructured conversions and imports; updates logging and event emissions to use typed objects.
TenantModule REST Handler
pkg/registry/core/tenantmodule/rest.go
Switches Get/List/Watch/ConvertToTable to strongly-typed TenantModule and TenantModuleList; adds explicit TypeMeta for objects and lists; removes unstructured conversions and imports; adjusts logs and error handling accordingly.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas to focus:
    • pkg/registry/apps/application/rest.go — verify HelmRelease ↔ Application conversion correctness, Update semantics, TypeMeta assignments, and Watch event payloads.
    • pkg/registry/core/tenantmodule/rest.go — ensure List/Watch produce properly typed items and ConvertToTable handles typed inputs.
    • go.mod replace directive — confirm intended fork/version and compatibility.
    • hack/e2e-test-openapi.bats — validate new kind assertions and namespace create/delete test behavior in CI.

Possibly related PRs

Suggested reviewers

  • lllamnyp
  • nbykov0

Poem

🐰 I hopped through lines with nimble feet,
Typed tenants, apps, all tidy and neat.
Tests that peek at lists and kind,
A namespace born, then left behind.
🥕 Cheers from the rabbit — code feels sweet!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: refactoring the apiserver to use typed objects (replacing unstructured representations) and fixing the UnstructuredList GVK issue, which aligns with the comprehensive changes across multiple files (go.mod, rest.go handlers, and e2e tests).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-namespace-removal

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca29fc8 and a05cc35.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (1)
  • go.mod (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • go.mod
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello Andrei Kvapil (@kvaps), I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the robustness and maintainability of the apiserver by transitioning its REST handlers from using generic unstructured objects to specific typed objects. This change not only streamlines the code by removing complex runtime conversions but also addresses a critical bug in how UnstructuredList objects resolve their GroupVersionKind (GVK), particularly when multiple types share a Go type. By integrating an upstream Kubernetes fix and enhancing OpenAPI validation, the system's API consistency and reliability are greatly improved.

Highlights

  • Refactor to Typed Objects: The apiserver REST handlers have been refactored to use appsv1alpha1.Application typed objects instead of unstructured.Unstructured, simplifying the codebase and eliminating the need for runtime conversions.
  • Fix UnstructuredList GVK Handling: An issue where UnstructuredList objects incorrectly used the first registered kind from typeToGVK has been fixed. The system now correctly uses the kind from the object's field, especially when multiple kinds are registered with the same Go type.
  • Upstream Kubernetes Fix Integration: This pull request incorporates an upstream fix from kubernetes/kubernetes#135537, enabling a short-circuit path for UnstructuredList objects similar to regular Unstructured objects, ensuring correct GVK resolution.
  • Dependency Updates and Apimachinery Replacement: Dependencies in go.mod and go.sum have been updated, and a replace directive for k8s.io/apimachinery to a local github.com/cozystack/apimachinery fork has been added to facilitate the GVK fix.
  • Enhanced E2E Testing: An end-to-end test for OpenAPI validation has been added, specifically including checks for TenantList and IngressList kinds to ensure the GVK fix is correctly applied and validated.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@dosubot dosubot Bot added the kind/bug Categorizes issue or PR as related to a bug label Dec 1, 2025

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request is a significant improvement, refactoring the apiserver REST handlers to use typed objects instead of unstructured.Unstructured. This greatly enhances type safety, readability, and maintainability by removing complex runtime conversions. The inclusion of the upstream fix for UnstructuredList GVK handling is also a crucial correction. My review includes a few suggestions to further improve the code, such as avoiding redundant code and a note on the dependency replacement for k8s.io/apimachinery. Overall, this is a high-quality contribution.

Comment thread go.mod
Comment thread pkg/registry/apps/application/rest.go Outdated
Comment thread pkg/registry/apps/application/rest.go Outdated
Comment thread pkg/registry/core/tenantmodule/rest.go Outdated
Comment thread pkg/registry/core/tenantmodule/rest.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/registry/apps/application/rest.go (1)

1048-1090: Critical: runtime.RawExtension{Object: &app} uses pointer to loop variable

In both table builders, you’re taking the address of the range loop variable:

for _, app := range apps {
    row := metav1.TableRow{
        Cells:  ...,
        Object: runtime.RawExtension{Object: &app},
    }
    table.Rows = append(table.Rows, row)
}

In Go, app is a single variable reused for each iteration; &app has the same address each time. All Row.Object entries will end up pointing to the last app in the slice, which is incorrect and can confuse clients that rely on the embedded object.

The same pattern appears in buildTableFromTenantModules in pkg/registry/core/tenantmodule/rest.go.

Fix by taking the address of the slice element instead of the loop variable, e.g.:

-func (r *REST) buildTableFromApplications(apps []appsv1alpha1.Application) metav1.Table {
+func (r *REST) buildTableFromApplications(apps []appsv1alpha1.Application) metav1.Table {
@@
-    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},
+            Cells:  []interface{}{app.GetName(), getReadyStatus(app.Status.Conditions), computeAge(app.GetCreationTimestamp().Time, now), getVersion(app.Status.Version)},
+            Object: runtime.RawExtension{Object: app},
         }
         table.Rows = append(table.Rows, row)
     }

And similarly for buildTableFromTenantModules:

- 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},
+        Cells:  []interface{}{module.GetName(), getReadyStatus(module.Status.Conditions), computeAge(module.GetCreationTimestamp().Time, now), getVersion(module.Status.Version)},
+        Object: runtime.RawExtension{Object: module},
     }

This is a correctness issue that will affect all table output.

🧹 Nitpick comments (11)
pkg/registry/core/tenantmodule/rest.go (3)

149-157: Log message uses kind wording but logs Resource

The log at Line 155 says “kind %s” but passes r.gvr.Resource, which is the resource name, not the kind. This is harmless but slightly misleading when debugging.

You could either change the format string to say “resource %s” or log r.gvk.Kind instead.


236-305: Prefer consistent TypeMeta construction and reuse NewList

List currently:

  • Builds []corev1alpha1.TenantModule and sets each item’s TypeMeta with a hard-coded APIVersion string.
  • Constructs TenantModuleList directly and sets TypeMeta.Kind to r.kindName + "List" with a hard-coded APIVersion.

Functionally this works, but you can simplify and make it more robust against future group/version changes:

  • Use r.gvk.GroupVersion().String() instead of "core.cozystack.io/v1alpha1" when setting TypeMeta.APIVersion.
  • Consider creating the list via r.NewList().(*corev1alpha1.TenantModuleList) so Kind/APIVersion always stay aligned with your REST storage config.

Example:

-        module.TypeMeta = metav1.TypeMeta{
-            APIVersion: "core.cozystack.io/v1alpha1",
-            Kind:       r.kindName,
-        }
+        module.TypeMeta = metav1.TypeMeta{
+            APIVersion: r.gvk.GroupVersion().String(),
+            Kind:       r.kindName,
+        }

-    moduleList := &corev1alpha1.TenantModuleList{}
-    moduleList.TypeMeta = metav1.TypeMeta{
-        APIVersion: "core.cozystack.io/v1alpha1",
-        Kind:       r.kindName + "List",
-    }
+    moduleList := r.NewList().(*corev1alpha1.TenantModuleList)

448-458: Watch path TypeMeta is consistent but could also use r.gvk

Setting module.TypeMeta in the watch code ensures events carry the correct apiVersion/kind, which is important for clients. For consistency with New/NewList and future-proofing across version bumps, you might want to switch APIVersion here to r.gvk.GroupVersion().String() rather than a literal string.

module.TypeMeta = metav1.TypeMeta{
-    APIVersion: "core.cozystack.io/v1alpha1",
+    APIVersion: r.gvk.GroupVersion().String(),
    Kind:       r.kindName,
}
pkg/registry/apps/application/rest.go (6)

142-188: Create: type assertion is fine, but consider apierrors.BadRequest and honoring createValidation

The explicit assertion to *appsv1alpha1.Application is good, but two improvements are worth considering:

  1. For a wrong type, returning a generic fmt.Errorf will likely surface as a 500. For API correctness, it’s more appropriate to return a 4xx, e.g.:
- app, ok := obj.(*appsv1alpha1.Application)
- if !ok {
-     return nil, fmt.Errorf("expected *appsv1alpha1.Application object, got %T", obj)
- }
+ app, ok := obj.(*appsv1alpha1.Application)
+ if !ok {
+     return nil, apierrors.NewBadRequest(
+         fmt.Sprintf("expected *appsv1alpha1.Application object, got %T", obj),
+     )
+ }
  1. The createValidation hook is currently unused. If callers rely on this (e.g., admission-style validation before persistence), you should invoke it before converting to HelmRelease:
if createValidation != nil {
    if err := createValidation(ctx, app); err != nil {
        return nil, err
    }
}

Also, for consistency with New/NewList, you may want to use r.gvk.GroupVersion().String() instead of the literal "apps.cozystack.io/v1alpha1" when setting convertedApp.TypeMeta.APIVersion.


231-239: Get: logging message uses “kind” but logs Resource

Similar to the TenantModule REST, the log line:

klog.V(6).Infof("Successfully retrieved and converted resource %s of kind %s", name, r.gvr.Resource)

labels the second value as “kind” while passing r.gvr.Resource. Either log r.gvk.Kind or change the message to “resource %s” for clarity.


321-385: List: typed items/list look good; tighten APIVersion handling

The typed items slice and ApplicationList construction look correct and align with the move away from unstructured. A couple of small consistency improvements:

  • When setting app.TypeMeta, prefer using r.gvk.GroupVersion().String() rather than a hard-coded "apps.cozystack.io/v1alpha1" string.
  • You’re already using r.NewList().(*appsv1alpha1.ApplicationList) which nicely centralizes TypeMeta initialization; that’s good and consistent with NewList().

Example for APIVersion:

app.TypeMeta = metav1.TypeMeta{
-    APIVersion: "apps.cozystack.io/v1alpha1",
+    APIVersion: r.gvk.GroupVersion().String(),
    Kind:       r.kindName,
}

428-501: Update: type assertion and return path are fine; align error type and APIVersion

The newObj assertion to *appsv1alpha1.Application and the conversion/update flow look sound, but similar to Create:

  • Consider returning apierrors.NewBadRequest on type mismatch instead of fmt.Errorf, so the client sees a proper 4xx.
  • When setting convertedApp.TypeMeta.APIVersion, use r.gvk.GroupVersion().String() instead of a literal string, to avoid divergence if group/version changes.

The final log ("Returning updated Application object: %+v") is quite verbose at V(6); if these objects grow large, you may eventually want to trim it to key fields, but that’s optional.


694-704: Watch: TypeMeta assignment is correct; reuse r.gvk for APIVersion

The watch path correctly:

  • Filters HelmReleases.
  • Converts to Application.
  • Sets TypeMeta.
  • Wraps &app in the event object.

To stay consistent with New/NewList and avoid hard-coded versions, I’d recommend:

app.TypeMeta = metav1.TypeMeta{
-    APIVersion: "apps.cozystack.io/v1alpha1",
+    APIVersion: r.gvk.GroupVersion().String(),
    Kind:       r.kindName,
}

The Object: &app usage here is fine because app is a fresh variable per event, not a loop variable reused across iterations.


727-733: helmReleaseName helper is now typed; consider extending if more callers appear

The helper now assumes a *appsv1alpha1.Application and falls back to "<unknown>" for other types. This is reasonable for internal use; if you ever pass other runtime.Object implementations, you might want to either:

  • Support additional known types explicitly, or
  • Fail loudly (e.g., log and return empty) instead of returning a sentinel string.

For the current usage, this is acceptable.

hack/e2e-test-openapi.bats (1)

23-43: List-level kind assertions look correct and align with the GVK fix

The added checks for .kind == "TenantList" and .kind == "IngressList" are a good regression guard for the UnstructuredList GVK behavior and complement the existing item-level kind checks. You are calling kubectl get --raw twice per endpoint, which is fine; if you ever want to shave a bit of test time, you could capture the JSON once per resource and reuse it for both assertions.

packages/system/cozystack-api/images/cozystack-api/Dockerfile (1)

6-8: Check whether apimachinery/ + patches/ and patch are still needed

Given that go.mod now uses a replace k8s.io/apimachinery => github.com/cozystack/apimachinery ..., the Dockerfile’s comment about a “local replace” plus copying apimachinery/ and patches/ (and installing patch) may be leftover from a previous approach.

If the build no longer applies local patches or uses those directories, you can drop the apk add patch and the extra COPY layers (and update the comment) to simplify the image and slightly speed up builds.

Also applies to: 14-19

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7f478f and bd8f04c.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • go.mod (2 hunks)
  • hack/e2e-test-openapi.bats (1 hunks)
  • packages/system/cozystack-api/images/cozystack-api/Dockerfile (1 hunks)
  • pkg/registry/apps/application/rest.go (11 hunks)
  • pkg/registry/core/tenantmodule/rest.go (7 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
{go.mod,go.sum}

📄 CodeRabbit inference engine (AGENTS.md)

Do not manually modify go.mod and go.sum files; use go get command instead

Files:

  • go.mod
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Use Controller-runtime patterns and kubebuilder style for Go code

Files:

  • pkg/registry/core/tenantmodule/rest.go
  • pkg/registry/apps/application/rest.go
🧬 Code graph analysis (1)
pkg/registry/apps/application/rest.go (3)
pkg/apis/apps/v1alpha1/types.go (2)
  • Application (59-67)
  • ApplicationList (27-32)
pkg/generated/applyconfiguration/apps/v1alpha1/application.go (1)
  • Application (39-46)
api/v1alpha1/groupversion_info.go (1)
  • GroupVersion (29-29)
🔇 Additional comments (4)
pkg/registry/core/tenantmodule/rest.go (1)

727-743: New/NewList implementations look correct and align with typed storage

New and NewList now return typed *TenantModule and *TenantModuleList with TypeMeta populated from r.gvk, which matches how the apps Application REST storage is implemented. This should play nicely with API discovery and list-kind handling.

pkg/registry/apps/application/rest.go (1)

1150-1165: New/NewList: good use of r.gvk for TypeMeta

New and NewList now return typed Application/ApplicationList with TypeMeta derived from r.gvk, which is exactly what you want for discovery and list-kind behavior. This should work well with the upstream UnstructuredList/typed-object fixes you’re adopting.

packages/system/cozystack-api/images/cozystack-api/Dockerfile (1)

1-2: Static binary on scratch is fine; confirm no dependence on OS CA bundle or other files

Building with CGO_ENABLED=0 and -extldflags=-static and then running from scratch is a solid minimal image pattern. Just make sure cozystack-api doesn’t rely on:

  • System CA certificates (e.g., /etc/ssl/certs) for outbound HTTPS, or
  • Other OS files (zoneinfo, shells, etc.).

If it does, consider copying in just the required assets (e.g., CA bundle) or moving to a small distroless base.

Also applies to: 26-32

go.mod (1)

9-18: Fork module wiring is correctly configured and verified

The k8s.io/apimachinery replace directive to github.com/cozystack/apimachinery is properly set up. Module verification passed, go mod tidy completed without errors, and the fork resolution is correct (pseudo-version v0.0.0-20251201201312-18e522a87614 with valid module metadata). The promoted direct dependencies (logr, zapr, client_golang, zap) are also correctly resolved. No further action needed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (6)
go.mod (1)

123-124: Ensure the replace directive is removed once upstream fix is released.

This replace directive is a temporary workaround for the UnstructuredList GVK fix from kubernetes/kubernetes#135537. Document a plan to remove this once the upstream fix is released and consumed.

pkg/registry/apps/application/rest.go (2)

322-322: Pre-allocate slice capacity for performance.

Initialize the items slice with capacity equal to len(hrList.Items) to avoid potential reallocations during append operations.

Apply this diff:

-	items := make([]appsv1alpha1.Application, 0)
+	items := make([]appsv1alpha1.Application, 0, len(hrList.Items))

180-184: Remove redundant TypeMeta assignment.

This TypeMeta assignment is redundant as it's already set within convertHelmReleaseToApplication (lines 921-924). This pattern appears in multiple methods (Create, Get, List, Update, Watch) and should be removed for cleaner code and to avoid potential inconsistencies.

pkg/registry/core/tenantmodule/rest.go (3)

236-237: Preallocate items slice to avoid reallocations

You can avoid potential reallocations in the hot List path by preallocating items with the known upper bound from hrList.Items (this matches earlier review feedback).

-	// Initialize TenantModule items array
-	items := make([]corev1alpha1.TenantModule, 0)
+	// Initialize TenantModule items array with capacity to avoid reallocations
+	items := make([]corev1alpha1.TenantModule, 0, len(hrList.Items))

Also applies to: 291-291


252-256: Avoid redundant TypeMeta assignment after conversion

convertHelmReleaseToTenantModule already initializes TypeMeta with APIVersion: "core.cozystack.io/v1alpha1" and Kind: r.kindName. Re‑assigning it here duplicates that responsibility and risks drift if the conversion helper is updated but this spot is not. Consider relying on the conversion function as the single source of truth and removing this block.


448-452: Reduce duplication of TypeMeta logic and avoid reusing the same module pointer across events

Two points here:

  1. The TypeMeta assignment duplicates what convertHelmReleaseToTenantModule already does, same as in List. It’s better to keep that centralized in the converter.

  2. module is a loop‑scoped variable reused across iterations of the watch goroutine. Taking &module and sending it on the channel means all events share the same underlying TenantModule instance, which can lead to subtle bugs if consumers keep references (they may observe the last value instead of the one at emission time). You can fix this by introducing a per‑iteration copy and taking its address:

-				// Explicitly set apiVersion and kind for TenantModule
-				module.TypeMeta = metav1.TypeMeta{
-					APIVersion: "core.cozystack.io/v1alpha1",
-					Kind:       r.kindName,
-				}
-
-				// Create watch event with TenantModule object
-				moduleEvent := watch.Event{
-					Type:   event.Type,
-					Object: &module,
-				}
+				// Optionally rely on conversion for TypeMeta and avoid pointer reuse across events
+				m := module // create a per-event copy to take the address of
+				moduleEvent := watch.Event{
+					Type:   event.Type,
+					Object: &m,
+				}

Also applies to: 457-457

🧹 Nitpick comments (1)
pkg/registry/core/tenantmodule/rest.go (1)

155-156: Clarify log message: use Kind instead of Resource name

The log currently says "kind %s" but logs r.gvr.Resource, which is the resource name (tenantmodules), not the kind (TenantModule). Prefer logging r.gvk.Kind (or r.kindName) to avoid confusion when debugging.

-	klog.V(6).Infof("Successfully retrieved and converted resource %s of kind %s", name, r.gvr.Resource)
+	klog.V(6).Infof("Successfully retrieved and converted resource %s of kind %s", name, r.gvk.Kind)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd8f04c and be1ff91.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • go.mod (2 hunks)
  • hack/e2e-test-openapi.bats (1 hunks)
  • pkg/registry/apps/application/rest.go (11 hunks)
  • pkg/registry/core/tenantmodule/rest.go (7 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Use Controller-runtime patterns and kubebuilder style for Go code

Files:

  • pkg/registry/core/tenantmodule/rest.go
  • pkg/registry/apps/application/rest.go
{go.mod,go.sum}

📄 CodeRabbit inference engine (AGENTS.md)

Do not manually modify go.mod and go.sum files; use go get command instead

Files:

  • go.mod
🧬 Code graph analysis (2)
pkg/registry/core/tenantmodule/rest.go (4)
api/v1alpha1/groupversion_info.go (1)
  • GroupVersion (29-29)
pkg/registry/apps/application/rest.go (1)
  • REST (77-86)
pkg/registry/core/tenantsecret/rest.go (7)
  • REST (159-163)
  • REST (181-181)
  • REST (182-182)
  • REST (183-185)
  • REST (186-186)
  • REST (190-190)
  • REST (498-498)
pkg/registry/core/tenantnamespace/rest.go (7)
  • REST (49-53)
  • REST (74-74)
  • REST (75-75)
  • REST (76-78)
  • REST (79-79)
  • REST (83-83)
  • REST (328-328)
pkg/registry/apps/application/rest.go (3)
pkg/apis/apps/v1alpha1/register.go (1)
  • Resource (48-50)
api/v1alpha1/groupversion_info.go (1)
  • GroupVersion (29-29)
pkg/registry/core/tenantmodule/rest.go (1)
  • REST (71-78)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (7)
hack/e2e-test-openapi.bats (2)

24-28: LGTM! List-level kind validation correctly implemented.

This new validation appropriately tests the fix for UnstructuredList GVK handling, ensuring the top-level .kind is "TenantList" as expected rather than potentially using an incorrect kind from typeToGVK registration.


34-38: LGTM! IngressList validation mirrors TenantList check.

The validation correctly ensures the top-level .kind is "IngressList", following the same pattern as the TenantList check and providing comprehensive coverage for the UnstructuredList GVK fix.

go.mod (1)

9-17: LGTM! Correct promotion of dependencies.

Promoting these logging and metrics dependencies from indirect to direct is appropriate given the refactoring to typed objects that now directly uses these packages in the REST handlers.

pkg/registry/apps/application/rest.go (1)

1150-1166: LGTM! Proper initialization in factory methods.

The TypeMeta initialization in New() and NewList() is correct and necessary. These factory methods are used by the REST framework to create properly initialized instances, unlike the redundant TypeMeta assignments in the CRUD methods.

pkg/registry/core/tenantmodule/rest.go (3)

294-299: List TypeMeta initialization looks consistent

Setting TenantModuleList.TypeMeta from the REST’s GVK and using r.kindName+"List" yields TenantModuleList, which matches the usual list-kind convention and aligns with how NewList is implemented.


725-733: New() implementation aligns with typed, kubebuilder-style REST storage

Returning a typed *corev1alpha1.TenantModule with TypeMeta initialized from r.gvk is consistent with controller-runtime / kubebuilder patterns and plays well with discovery and OpenAPI.


736-743: NewList() list-kind initialization looks good

Using TenantModuleList with APIVersion from r.gvk and Kind: r.kindName + "List" matches the REST GVK and the list-kind convention, and is consistent with New().

Comment thread pkg/registry/core/tenantmodule/rest.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/registry/apps/application/rest.go (1)

1030-1036: Fix pointer-to-loop-variable bug in table rows.

Same issue as in tenantmodule/rest.go: the range variable app is reused across iterations, so &app causes all rows to reference the same memory location.

Apply this diff to fix the issue:

 	for _, app := range apps {
+		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)
 	}

Alternatively, use index-based iteration:

-	for _, app := range apps {
+	for i := range apps {
 		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},
+			Cells:  []interface{}{apps[i].GetName(), getReadyStatus(apps[i].Status.Conditions), computeAge(apps[i].GetCreationTimestamp().Time, now), getVersion(apps[i].Status.Version)},
+			Object: runtime.RawExtension{Object: &apps[i]},
 		}
 		table.Rows = append(table.Rows, row)
 	}
♻️ Duplicate comments (2)
go.mod (1)

122-124: Replace directive with future-dated pseudo-version.

The replace directive uses a pseudo-version with a future date (20251201...). This has already been flagged in a previous review. Ensure this temporary fork is tracked for removal once the upstream fix from kubernetes/kubernetes#135537 is released in a stable k8s.io/apimachinery version.

Based on learnings, go.mod and go.sum should generally be modified using go get commands rather than manual edits. However, replace directives are a valid exception since they cannot be added via go get.

pkg/registry/core/tenantmodule/rest.go (1)

638-644: Fix pointer-to-loop-variable bug in table rows.

The range variable module is reused across iterations in Go, so &module in runtime.RawExtension{Object: &module} causes every row's Object to point to the same memory location (the last item). This was flagged in a previous review.

Apply this diff to fix the issue:

 	for _, module := range modules {
+		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)
 	}

Alternatively, use index-based iteration:

-	for _, module := range modules {
+	for i := range modules {
 		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},
+			Cells:  []interface{}{modules[i].GetName(), getReadyStatus(modules[i].Status.Conditions), computeAge(modules[i].GetCreationTimestamp().Time, now), getVersion(modules[i].Status.Version)},
+			Object: runtime.RawExtension{Object: &modules[i]},
 		}
 		table.Rows = append(table.Rows, row)
 	}
🧹 Nitpick comments (2)
pkg/registry/core/tenantmodule/rest.go (1)

649-669: Potential issue: Address of parameter in single-item table.

The function receives module by value and takes its address for Object. While this works for a single row, it's inconsistent with best practices. Consider passing by pointer or documenting the intent.

-func (r *REST) buildTableFromTenantModule(module corev1alpha1.TenantModule) metav1.Table {
+func (r *REST) buildTableFromTenantModule(module *corev1alpha1.TenantModule) metav1.Table {
 	table := metav1.Table{
 		ColumnDefinitions: []metav1.TableColumnDefinition{
 			{Name: "NAME", Type: "string", Description: "Name of the TenantModule", Priority: 0},
 			{Name: "READY", Type: "string", Description: "Ready status of the TenantModule", Priority: 0},
 			{Name: "AGE", Type: "string", Description: "Age of the TenantModule", Priority: 0},
 			{Name: "VERSION", Type: "string", Description: "Version of the TenantModule", Priority: 0},
 		},
 		Rows: []metav1.TableRow{},
 	}
 	now := time.Now()

 	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)

 	return table
 }

Then update the caller at line 598:

-		table = r.buildTableFromTenantModule(*obj)
+		table = r.buildTableFromTenantModule(obj)
pkg/registry/apps/application/rest.go (1)

1041-1061: Consider passing by pointer for consistency.

Similar to tenantmodule/rest.go, the function receives app by value and takes its address. Consider passing by pointer for consistency and to avoid the copy.

-func (r *REST) buildTableFromApplication(app appsv1alpha1.Application) metav1.Table {
+func (r *REST) buildTableFromApplication(app *appsv1alpha1.Application) metav1.Table {
 	...
 	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},
 	}

Update the caller at line 990:

-		table = r.buildTableFromApplication(*obj)
+		table = r.buildTableFromApplication(obj)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be1ff91 and 52d6cf0.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • go.mod (2 hunks)
  • hack/e2e-test-openapi.bats (1 hunks)
  • pkg/registry/apps/application/rest.go (10 hunks)
  • pkg/registry/core/tenantmodule/rest.go (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • hack/e2e-test-openapi.bats
🧰 Additional context used
📓 Path-based instructions (2)
{go.mod,go.sum}

📄 CodeRabbit inference engine (AGENTS.md)

Do not manually modify go.mod and go.sum files; use go get command instead

Files:

  • go.mod
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Use Controller-runtime patterns and kubebuilder style for Go code

Files:

  • pkg/registry/core/tenantmodule/rest.go
  • pkg/registry/apps/application/rest.go
🧠 Learnings (1)
📚 Learning: 2025-11-27T11:21:45.808Z
Learnt from: CR
Repo: cozystack/cozystack PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T11:21:45.808Z
Learning: Applies to {go.mod,go.sum} : Do not manually modify `go.mod` and `go.sum` files; use `go get` command instead

Applied to files:

  • go.mod
🧬 Code graph analysis (1)
pkg/registry/apps/application/rest.go (2)
pkg/generated/applyconfiguration/apps/v1alpha1/application.go (1)
  • Application (39-46)
api/v1alpha1/groupversion_info.go (1)
  • GroupVersion (29-29)
🔇 Additional comments (17)
go.mod (1)

9-10: LGTM: Dependencies promoted to direct.

The promotion of go-logr/logr, go-logr/zapr, prometheus/client_golang, and go.uber.org/zap from indirect to direct dependencies aligns with the typed REST handling refactor.

Also applies to: 14-14, 17-17

pkg/registry/core/tenantmodule/rest.go (6)

149-150: LGTM: Typed TenantModule returned from Get.

The migration from unstructured to typed TenantModule is correctly implemented, and the TypeMeta is set in the conversion function.


230-231: LGTM: Slice initialized with capacity.

The items slice is now pre-allocated with capacity matching len(hrList.Items), addressing the previous performance suggestion.


282-289: LGTM: TenantModuleList with proper TypeMeta.

The TenantModuleList is correctly initialized with APIVersion and Kind, ensuring proper GVK handling.


436-440: LGTM: Watch emits typed TenantModule.

The watch correctly emits a pointer to the converted module. Since module is a fresh value from ConvertHelmReleaseToTenantModule each iteration, taking its address is safe here.


594-599: LGTM: ConvertToTable handles typed objects.

The switch now correctly handles *corev1alpha1.TenantModuleList and *corev1alpha1.TenantModule, aligning with the typed object migration.


708-725: LGTM: New and NewList with proper TypeMeta.

Both methods correctly initialize typed objects with APIVersion and Kind derived from the REST's gvk, ensuring consistent GVK handling.

pkg/registry/apps/application/rest.go (10)

144-147: LGTM: Create accepts typed Application.

The type assertion to *appsv1alpha1.Application is correct and provides a clear error message on type mismatch.


180-182: LGTM: Create returns typed Application.

Returning a pointer to the converted Application is correct.


225-227: LGTM: Get returns typed Application.

The Get method correctly returns a pointer to the typed Application.


309-310: LGTM: Slice initialized with capacity.

The items slice is pre-allocated with capacity matching len(hrList.Items), addressing previous performance feedback.


360-363: LGTM: List uses NewList for ApplicationList creation.

Using r.NewList() ensures consistent TypeMeta initialization across all code paths.


411-416: LGTM: Update type assertion.

The type assertion to *appsv1alpha1.Application is correct with clear error messaging.


474-477: LGTM: Update returns typed Application.

Returning a pointer to the converted Application is correct.


670-674: LGTM: Watch emits typed Application.

The watch correctly emits a pointer to the converted app. Since app is a fresh value from ConvertHelmReleaseToApplication each iteration, this is safe.


698-703: LGTM: helmReleaseName helper updated.

The helper correctly handles the typed *appsv1alpha1.Application.


1119-1136: LGTM: New and NewList with proper TypeMeta.

Both methods correctly initialize typed objects with APIVersion and Kind derived from the REST's gvk.

…st 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
#1630, which was reverted in
#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 <[email protected]>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
hack/e2e-test-openapi.bats (1)

46-53: Consider bounding namespace deletion time

The create/delete namespace test is useful to catch stuck finalizers. To avoid a potentially hanging e2e run in pathological cases, consider adding an explicit timeout, e.g. kubectl delete ns ... --wait=true --timeout=5m, so failures surface as test errors rather than indefinite hangs.

pkg/registry/core/tenantmodule/rest.go (1)

149-151: Minor logging wording nit in Get

The log message says “of kind %s” but logs r.gvr.Resource (e.g. tenantmodules). Consider changing the format string or argument to avoid “kind” vs “resource” confusion; the returned object itself looks correct.

pkg/registry/apps/application/rest.go (3)

178-182: Create log message text is slightly misleading

In Create, the log line says “Successfully retrieved and converted resource …” though this path is performing a create-then-convert. Consider rephrasing to “created and converted” (or similar) to avoid confusion when debugging.


218-227: Get returns typed Application with consistent TypeMeta

Returning &convertedApp after ConvertHelmReleaseToApplication keeps the REST Get path fully typed. As in the TenantModule REST, the log message mentions “kind” but logs r.gvr.Resource; consider aligning wording/argument, but behavior is otherwise correct.


472-477: Verbose logging of full updated object may be heavy

klog.V(6).Infof("Returning updated Application object: %+v", convertedApp) can produce very large logs if specs are big. Given V(6) is already quite verbose this is acceptable, but you might consider logging only metadata (name/namespace/resourceVersion) to keep debug output leaner.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52d6cf0 and ca29fc8.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • go.mod (2 hunks)
  • hack/e2e-test-openapi.bats (1 hunks)
  • pkg/registry/apps/application/rest.go (12 hunks)
  • pkg/registry/core/tenantmodule/rest.go (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • go.mod
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Use Controller-runtime patterns and kubebuilder style for Go code

Files:

  • pkg/registry/apps/application/rest.go
  • pkg/registry/core/tenantmodule/rest.go
🧬 Code graph analysis (2)
pkg/registry/apps/application/rest.go (2)
api/v1alpha1/groupversion_info.go (1)
  • GroupVersion (29-29)
pkg/registry/registry.go (1)
  • REST (26-28)
pkg/registry/core/tenantmodule/rest.go (4)
api/v1alpha1/groupversion_info.go (1)
  • GroupVersion (29-29)
pkg/registry/apps/application/rest.go (1)
  • REST (77-86)
pkg/registry/core/tenantsecret/rest.go (7)
  • REST (159-163)
  • REST (181-181)
  • REST (182-182)
  • REST (183-185)
  • REST (186-186)
  • REST (190-190)
  • REST (498-498)
pkg/registry/core/tenantnamespace/rest.go (7)
  • REST (49-53)
  • REST (74-74)
  • REST (75-75)
  • REST (76-78)
  • REST (79-79)
  • REST (83-83)
  • REST (328-328)
🔇 Additional comments (15)
hack/e2e-test-openapi.bats (1)

23-38: Kind checks for tenants/ingresses look good

Validating both the list kind (TenantList/IngressList) and the first item kind strengthens the regression coverage around TypeMeta/Kind wiring. No issues from a test logic standpoint.

pkg/registry/core/tenantmodule/rest.go (6)

230-293: List preallocation and TypeMeta wiring look correct

Preallocating items with len(hrList.Items) and then populating TenantModuleList with explicit TypeMeta (core.cozystack.io/v1alpha1, TenantModuleList) is aligned with the typed REST approach and should satisfy list-kind OpenAPI expectations. No functional issues spotted.


436-440: Watch now emits typed TenantModule objects

Emitting watch.Event{Object: &module} where module is a fully-populated TenantModule (with TypeMeta set in the converter) is consistent with the new typed surface and avoids unstructured conversions. This looks correct.


593-599: ConvertToTable switch restricted to TenantModule types

Handling only *corev1alpha1.TenantModuleList and *corev1alpha1.TenantModule here simplifies the code and matches the move away from unstructured.Unstructured. The error path for unsupported types is still appropriate.


625-648: Fixed table row object pointer construction for lists

Using for i := range modules with module := &modules[i] and storing runtime.RawExtension{Object: module} ensures each row’s Object points to the correct list element, avoiding the previous pointer-to-loop-variable pitfall.


650-671: Single-object table builder uses stable per-row pointer

Copying the value into m := module and taking &m gives each row its own Application instance, which is safe even if the caller later mutates the original argument. This matches the intent of the earlier pointer-safety fixes.


709-727: New/NewList correctly initialize TypeMeta

Initializing TypeMeta in New and NewList from r.gvk.GroupVersion().String() and r.kindName/r.kindName+"List" ensures both singular and list objects have the right APIVersion/Kind when constructed by the apiserver, which is important for discovery and OpenAPI.

pkg/registry/apps/application/rest.go (8)

141-147: Create now expects typed Application — assertion is appropriate

Asserting obj.(*appsv1alpha1.Application) at the REST boundary matches the new typed API surface and removes unstructured conversions. The error message is clear if the wrong type ever arrives.


309-367: List preallocation and list construction look solid

Preallocating items := make([]appsv1alpha1.Application, 0, len(hrList.Items)) and building appList := r.NewList().(*appsv1alpha1.ApplicationList) ensures efficient slice growth and consistent TypeMeta (APIVersion/Kind) for the list object. This also dovetails with the UnstructuredList GVK fix.


410-416: Update type assertion matches Create/Get

The type assertion to *appsv1alpha1.Application in Update keeps the API consistent across CRUD operations, and the error message includes the actual type, which is helpful for debugging mis-wired callers.


646-674: Watch now emits typed Application objects

Creating watch.Event{Object: &app} aligns the watch stream with the typed REST surface. Given app is a value produced per event and already has TypeMeta set in the converter, this is a clean replacement for the prior unstructured-based watch.


697-703: helmReleaseName helper now matches the typed API

Limiting helmReleaseName to handle *appsv1alpha1.Application directly matches the new REST expectations. Returning "<unknown>" for other types is a reasonable fallback for logging-only usage.


1017-1040: Application list table builder uses safe per-element pointers

The for i := range apps { app := &apps[i]; ... Object: runtime.RawExtension{Object: app} } pattern avoids the pointer-to-loop-variable issue and ensures each row points to the correct list element. Column definitions and cell content look correct.


1042-1063: Single-Application table builder uses independent copy

Copying into a := app and storing &a in the Object field avoids accidental aliasing if the original value is reused or mutated after building the table. This is consistent with the list builder’s pointer-safety approach.


1120-1138: New/NewList correctly set TypeMeta for Applications

Initializing TypeMeta from r.gvk.GroupVersion().String() and r.kindName/r.kindName+"List" in New and NewList ensures server-created Application and ApplicationList objects carry accurate APIVersion/Kind, which is important for discovery and for the OpenAPI/List-kind tests you added.

@insignia96

Copy link
Copy Markdown
Contributor

I can confirm from my testing that this addresses my issue #1673 when using cozystack-api patched with this PR.

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Dec 9, 2025
@kvaps Andrei Kvapil (kvaps) added the kind/backport Categorizes issue or PR as requiring a backport to the current release line label Dec 10, 2025
@kvaps
Andrei Kvapil (kvaps) merged commit e3b96e1 into main Dec 10, 2025
35 of 39 checks passed
@kvaps
Andrei Kvapil (kvaps) deleted the fix-namespace-removal branch December 10, 2025 12:22
@github-actions

Copy link
Copy Markdown

Andrei Kvapil (kvaps) added a commit that referenced this pull request Dec 10, 2025
…s and fix UnstructuredList GVK (#1709)

# Description
Backport of #1679 to `release-0.38`.
Andrei Kvapil (kvaps) added a commit that referenced this pull request Jan 8, 2026
…st 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
#1630, which was reverted in
#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 -->
Andrei Kvapil (kvaps) added a commit that referenced this pull request Jan 8, 2026
…st 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
#1630, which was reverted in
#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 -->
Andrei Kvapil (kvaps) added a commit that referenced this pull request Jan 9, 2026
…st 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
#1630, which was reverted in
#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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/backport Categorizes issue or PR as requiring a backport to the current release line kind/bug Categorizes issue or PR as related to a bug lgtm This PR has been approved by a maintainer size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants