Skip to content

[api] Fix representation of dynamic list kinds - #1630

Merged
Timofei Larkin (lllamnyp) merged 1 commit into
mainfrom
fix/cozy-api-typing
Nov 13, 2025
Merged

Timofei Larkin (lllamnyp) merged 1 commit into
mainfrom
fix/cozy-api-typing

Conversation

@lllamnyp

@lllamnyp Timofei Larkin (lllamnyp) commented Nov 13, 2025 •

Copy link
Copy Markdown
Member

What this PR does

This patch fixes an issue with the Cozystack API server that causes it to respond with the first registered dynamic list kind when listing dynamic resources. E.g., when running k get postgreses, the raw JSON response from the cozystack API would be

{
  "apiVersion":"apps.cozystack.io/v1alpha1",
  "kind":"BootBoxList",
  "items": [
    {
      "apiVersion":"apps.cozystack.io/v1alpha1",
      "kind":"Postgres",
      ...
    },
    ...
  ],
  ...
}

The root cause is the way the Typer interface is implemented for the runtime.Scheme where the dynamic types are registered. Since the base type for all dynamic types is a &cozyv1alpha1.Application{}, all types are registered under the same key and the Typer defaults to the first GroupVersionKind that was registered. Only when a correctly formed &unstructured.Unstructured{} is returned by the API, is this resolving logic circumvented and the GroupVersionKind is instead inferred from the fields of the returned object. Even an UnstructuredList is not acceptable as a return type, instead the items key should be directly set on the underlying map[string]interface{}.

This patch implements the changes detailed above. Additionally, the following features, fixes, and improvements are added:

  • Makefile targets to build and run the Cozystack API locally, against a Kubernetes server in the environment's KUBECONFIG. Debugging with Delve is also supported.
  • CI tests are added to verify the new changes.
  • A bug in the registration of the corev1alpha1 types is fixed.
  • Updated the ConvertToTable function to properly handle list kinds which might be of the &unstructured.Unstructured{} concrete type (not an UnstructuredList).
  • The scheme used by the API server's Client and WatchClient is separated from the scheme used to serve dynamic types.
  • The client config for reading the runtime configuration now uses the controller-runtime, which handles flags and environment variables properly, unlike clientcmd.

Release note

[api] Fix incorrect list kind for list requests to the Cozystack API for
dynamic resources. Add Makefile targets for local testing. Minor schema
building improvements.

Summary by CodeRabbit

  • Tests

    • Added an end-to-end test validating Kubernetes API kinds for tenants and ingresses.
  • Chores

    • Improved local development: ignore local API server config, added run/debug targets with local TLS tooling, and added an OpenSSL config for CSR generation.
  • Refactor

    • Internal API server and registry storage reworked; may affect integrations expecting concrete resource types.

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

coderabbitai Bot commented Nov 13, 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

Adds an E2E OpenAPI kind-check test, local API-server run/debug Makefile targets and OpenSSL CSR config, switches manager and watch client to a per-package manager scheme with logging, replaces kubeconfig retrieval with controller-runtime GetConfig, and refactors Application REST storage to use unstructured objects.

Changes

Cohort / File(s) Summary
E2E test
hack/e2e-test-openapi.bats
Adds a "Test kinds" block that queries API paths for tenants and ingresses, extracts kinds with jq, and asserts TenantList/Tenant and IngressList/Ingress for collection and first-item resources.
Local dev & TLS
packages/system/cozystack-api/.gitignore, packages/system/cozystack-api/Makefile, packages/system/cozystack-api/cozystack-api-openssl.cnf
Adds apiserver.local.config/ to .gitignore; introduces run-local and debug Makefile targets to generate PKI (CA, client, server certs), build/run the binary (or run under dlv) with TLS and kubeconfig flags; adds OpenSSL CSR config with CN cozystack-api and SAN 127.0.0.1.
API server wiring & config
pkg/apiserver/apiserver.go, pkg/cmd/server/start.go
Introduces a per-package mgrScheme and registers core/HelmRelease/RBAC to it; sets up logging via Zap and klog binding; creates manager and watch client using mgrScheme; replaces clientcmd.BuildConfigFromFlags with controller-runtime GetConfig() for kubeconfig retrieval.
REST storage → unstructured
pkg/registry/apps/application/rest.go
Refactors REST storage to operate on *unstructured.Unstructured for New(), NewList(), List, Create, Update, Watch, and ConvertToTable; sets GroupVersionKind on Unstructured objects and handles list items as unstructured arrays instead of concrete Application/ApplicationList.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as server start
    participant Config as controller-runtime GetConfig
    participant Logger as ctrl.SetLogger (Zap + klog)
    participant Manager as controller-runtime Manager (mgrScheme)
    participant Registrar as type registration (core/HelmRelease/RBAC)
    participant WatchClient as Watch client (uses mgrScheme)

    CLI->>Config: call GetConfig()
    Config-->>CLI: kubeconfig
    CLI->>Logger: initialize Zap & bind klog
    CLI->>Manager: NewManager(cfg, Scheme=mgrScheme)
    Manager->>Registrar: register types on mgrScheme
    Manager->>WatchClient: create Watch client using mgrScheme
    Note right of WatchClient: Watch client uses mgrScheme for GVK resolution
    Manager-->>CLI: started manager (runs controllers)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect pkg/registry/apps/application/rest.go for correct unstructured↔concrete conversions, list item handling, resourceVersion preservation, and GVK assignments.
  • Verify pkg/apiserver/apiserver.go for scheme scoping and that the watch client uses the same scheme.
  • Check packages/system/cozystack-api/Makefile run-local/debug steps and the OpenSSL config alignment with runtime flags.

Possibly related PRs

Suggested reviewers

  • klinch0

Poem

🐰 I hopped through schemes and tucked logs in tight,
Unstructured carrots gleam in the moonlight.
Certs snug in burrow, tests chase the sun,
The API springs forward — small victories won. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[api] Fix representation of dynamic list kinds' directly and clearly summarizes the main change: fixing how list kinds are represented in the API for dynamic resources.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/cozy-api-typing

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 Timofei Larkin (@lllamnyp), 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 resolves a critical bug in the Cozystack API server where dynamic resource list requests were returning an incorrect "kind" in their JSON responses, leading to client-side misinterpretation. The core fix involves a refined approach to how dynamic types are registered and how list objects are constructed and returned, ensuring accurate "apiVersion" and "kind" fields. Additionally, the PR significantly improves the developer experience by adding "Makefile" targets for local API server setup and debugging, enhances internal schema management by separating "runtime.Scheme" instances, and updates the client configuration process for better compatibility and robustness. New CI tests have also been introduced to prevent regressions.

Highlights

  • Dynamic List Kind Fix: Corrected an issue where the Cozystack API server returned the wrong "kind" for dynamic resource lists (e.g., "BootBoxList" instead of "PostgresList").
  • API Response Structure: Modified the API to return "&unstructured.Unstructured{}" directly for list resources, setting the "items" field on its underlying map, circumventing "runtime.Scheme"'s "Typer" interface limitations.
  • Local Development Tools: Introduced "Makefile" targets ("run-local", "debug") to simplify building, running, and debugging the Cozystack API server locally against a Kubernetes cluster.
  • CI Test Coverage: Added new end-to-end tests ("hack/e2e-test-openapi.bats") to validate the correct "kind" representation for dynamic list and item resources.
  • Schema Management Refinement: Separated the "runtime.Scheme" used by the API server for dynamic types from the scheme used by the "controller-runtime" manager's client and watch client, improving type registration.
  • Client Configuration Update: Switched the Kubernetes client configuration mechanism from "clientcmd" to "controller-runtime/pkg/client/config" for better flag and environment variable handling.
  • Core Type Registration Fix: Addressed a bug in the registration of "corev1alpha1" types.
  • Table Conversion Improvement: Enhanced the "ConvertToTable" function to correctly process "&unstructured.Unstructured{}" objects when they represent lists.
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 Nov 13, 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 introduces several important fixes and improvements, most notably correcting the representation of dynamic list kinds in the API server response. The changes to use unstructured.Unstructured for dynamic types are well-implemented. I've provided a few suggestions to improve the new test case, enhance code readability in the ConvertToTable function, and fix a potential type inconsistency that could lead to a runtime error.

return &appsv1alpha1.ApplicationList{}
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(r.gvk.GroupVersion().WithKind(r.kindName + "List"))
obj.Object["items"] = make([]interface{}, 0)

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.

high

The items slice is initialized here as make([]interface{}, 0), but the List function populates it with a slice of type []unstructured.Unstructured. This type inconsistency can lead to a panic in ConvertToTable if it receives an empty list that hasn't been processed by the List function, as the type assertion items.([]unstructured.Unstructured) would fail. To ensure type safety and prevent this potential runtime error, the items slice should be consistently typed.

  obj.Object["items"] = make([]unstructured.Unstructured, 0)

Comment on lines +23 to +44
@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
}

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.

medium

The kubectl get --raw commands for tenants and ingresses are executed multiple times within this test. To improve efficiency and readability, you could store the output of each command in a variable and reuse it for the subsequent checks.

@test "Test kinds" {
  tenants_raw=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants)
  val=$(echo "$tenants_raw" | jq -r '.kind')
  if [ "$val" != "TenantList" ]; then
    echo "Expected kind to be TenantList, got $val"
    exit 1
  fi
  val=$(echo "$tenants_raw" | jq -r '.items[0].kind')
  if [ "$val" != "Tenant" ]; then
    echo "Expected kind to be Tenant, got $val"
    exit 1
  fi

  ingresses_raw=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses)
  val=$(echo "$ingresses_raw" | jq -r '.kind')
  if [ "$val" != "IngressList" ]; then
    echo "Expected kind to be IngressList, got $val"
    exit 1
  fi
  val=$(echo "$ingresses_raw" | jq -r '.items[0].kind')
  if [ "$val" != "Ingress" ]; then
    echo "Expected kind to be Ingress, got $val"
    exit 1
  fi
}

Comment on lines +1061 to +1088
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
}

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.

medium

This block of code for handling list-like unstructured.Unstructured objects is a bit difficult to follow due to the for {} loop being used for control flow and the separate if apps != nil check. This can be refactored to be more idiomatic and improve readability by using nested if statements, which clearly expresses the intent to try parsing as a list and then fall through to single-object parsing if that fails.

    if items, ok := obj.Object["items"]; ok {
      if objects, ok := items.([]unstructured.Unstructured); ok {
        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)
        }
        table = r.buildTableFromApplications(apps)
        table.ListMeta.ResourceVersion = obj.GetResourceVersion()
        break
      }
    }

@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

🧹 Nitpick comments (2)
pkg/registry/apps/application/rest.go (1)

1061-1088: Handle unstructured lists correctly in ConvertToTable.

The updated logic extracts items from an *unstructured.Unstructured that contains an "items" field, which is appropriate for the new list representation. The fall-through to single-object handling when items are not present maintains backward compatibility.

One minor note: the for { ... break } pattern on lines 1062-1083 is an unconventional way to handle conditional logic. Consider refactoring to explicit if statements for clarity:

-		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 {
+		if items, ok := obj.Object["items"]; ok {
+			if objects, ok := items.([]unstructured.Unstructured); ok {
+				apps := make([]appsv1alpha1.Application, 0, len(objects))
+				for i := range objects {
+					var a appsv1alpha1.Application
+					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)
+				}
+				table = r.buildTableFromApplications(apps)
+				table.ListMeta.ResourceVersion = obj.GetResourceVersion()
+			}
+		} else {
-			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()
+			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()
+		}
packages/system/cozystack-api/Makefile (1)

7-14: Consider certificate cleanup and reuse.

The run-local target generates new certificates each time it's invoked. Consider these improvements:

  1. Certificate cleanup: Add a cleanup mechanism or use the ignored apiserver.local.config/ directory instead of /tmp for better organization.
  2. Certificate reuse: Check if certificates already exist before regenerating them to speed up subsequent runs.

Example refactor:

 run-local:
-	openssl req -nodes -new -x509 -keyout /tmp/ca.key -out /tmp/ca.crt -subj "/CN=kube-ca"
-	openssl req -out /tmp/client.csr -new -newkey rsa:2048 -nodes -keyout /tmp/client.key -subj "/C=US/ST=SomeState/L=L/OU=Dev/CN=development/O=system:masters"
-	openssl x509 -req -days 365 -in /tmp/client.csr -CA /tmp/ca.crt -CAkey /tmp/ca.key -set_serial 01 -sha256 -out /tmp/client.crt
-	openssl req -out /tmp/apiserver.csr -new -newkey rsa:2048 -nodes -keyout /tmp/apiserver.key -subj "/CN=cozystack-api" -config cozystack-api-openssl.cnf
-	openssl x509 -req -days 365 -in /tmp/apiserver.csr -CA /tmp/ca.crt -CAkey /tmp/ca.key -set_serial 01 -sha256 -out /tmp/apiserver.crt -extensions v3_req -extfile cozystack-api-openssl.cnf
+	mkdir -p apiserver.local.config
+	test -f apiserver.local.config/ca.crt || openssl req -nodes -new -x509 -keyout apiserver.local.config/ca.key -out apiserver.local.config/ca.crt -subj "/CN=kube-ca"
+	test -f apiserver.local.config/client.crt || openssl req -out apiserver.local.config/client.csr -new -newkey rsa:2048 -nodes -keyout apiserver.local.config/client.key -subj "/C=US/ST=SomeState/L=L/OU=Dev/CN=development/O=system:masters"
+	test -f apiserver.local.config/client.crt || openssl x509 -req -days 365 -in apiserver.local.config/client.csr -CA apiserver.local.config/ca.crt -CAkey apiserver.local.config/ca.key -set_serial 01 -sha256 -out apiserver.local.config/client.crt
+	test -f apiserver.local.config/apiserver.crt || openssl req -out apiserver.local.config/apiserver.csr -new -newkey rsa:2048 -nodes -keyout apiserver.local.config/apiserver.key -subj "/CN=cozystack-api" -config cozystack-api-openssl.cnf
+	test -f apiserver.local.config/apiserver.crt || openssl x509 -req -days 365 -in apiserver.local.config/apiserver.csr -CA apiserver.local.config/ca.crt -CAkey apiserver.local.config/ca.key -set_serial 01 -sha256 -out apiserver.local.config/apiserver.crt -extensions v3_req -extfile cozystack-api-openssl.cnf
 	CGO_ENABLED=0 go build -o /tmp/cozystack-api ../../../cmd/cozystack-api/main.go
-	/tmp/cozystack-api --client-ca-file /tmp/ca.crt --tls-cert-file /tmp/apiserver.crt --tls-private-key-file /tmp/apiserver.key --secure-port 6443 --kubeconfig $(KUBECONFIG) --authorization-kubeconfig $(KUBECONFIG) --authentication-kubeconfig $(KUBECONFIG)
+	/tmp/cozystack-api --client-ca-file apiserver.local.config/ca.crt --tls-cert-file apiserver.local.config/apiserver.crt --tls-private-key-file apiserver.local.config/apiserver.key --secure-port 6443 --kubeconfig $(KUBECONFIG) --authorization-kubeconfig $(KUBECONFIG) --authentication-kubeconfig $(KUBECONFIG)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d6af84 and 8de39f6.

📒 Files selected for processing (7)
  • hack/e2e-test-openapi.bats (1 hunks)
  • packages/system/cozystack-api/.gitignore (1 hunks)
  • packages/system/cozystack-api/Makefile (1 hunks)
  • packages/system/cozystack-api/cozystack-api-openssl.cnf (1 hunks)
  • pkg/apiserver/apiserver.go (5 hunks)
  • pkg/cmd/server/start.go (2 hunks)
  • pkg/registry/apps/application/rest.go (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
pkg/apiserver/apiserver.go (4)
pkg/apis/apps/install/install.go (1)
  • Install (26-29)
pkg/apis/core/v1alpha1/register.go (1)
  • AddToScheme (31-31)
pkg/apis/apps/v1alpha1/register.go (1)
  • AddToScheme (32-32)
api/v1alpha1/groupversion_info.go (1)
  • AddToScheme (35-35)
pkg/registry/apps/application/rest.go (7)
pkg/apis/apps/v1alpha1/types.go (1)
  • Application (59-67)
pkg/generated/applyconfiguration/apps/v1alpha1/application.go (1)
  • Application (39-46)
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/core/tenantmodule/rest.go (1)
  • REST (72-79)
pkg/registry/registry.go (1)
  • REST (26-28)
api/v1alpha1/groupversion_info.go (1)
  • GroupVersion (29-29)
⏰ 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 (13)
packages/system/cozystack-api/.gitignore (1)

1-1: LGTM!

The .gitignore entry appropriately excludes local development artifacts generated by the new run-local and debug Makefile targets.

pkg/cmd/server/start.go (2)

49-49: LGTM!

The import of controller-runtime's config package is appropriate for the kubeconfig retrieval changes below.


153-156: LGTM!

Switching to k8sconfig.GetConfig() is the idiomatic approach when using controller-runtime and properly aligns with the rest of the codebase's dependency on controller-runtime.

pkg/apiserver/apiserver.go (2)

63-67: LGTM!

The logging initialization with zap in development mode and klog integration is appropriate for debugging the API server.


53-54: LGTM!

Separating mgrScheme (for the manager's client and watch client) from Scheme (for API serving and dynamic types) is the correct approach to prevent the Typer issue described in the PR objectives. This ensures list kinds are correctly inferred for dynamic resources.

Also applies to: 72-81, 145-145, 175-175

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

1225-1227: LGTM!

Returning an *unstructured.Unstructured with the correct GVK is the key fix for the list kind representation issue. This allows the runtime.Scheme's Typer to correctly infer the kind for each distinct resource type.


1232-1235: LGTM!

Creating the list as an *unstructured.Unstructured with the list kind (e.g., "PostgresList") and an empty items array is correct. This ensures the Typer returns the proper list kind instead of defaulting to the first registered dynamic list kind.


392-394: LGTM!

Populating the list by calling r.NewList() and setting Object["items"] directly is the correct approach for building an unstructured list response. The resource version is properly preserved.


517-522: LGTM!

Setting the GVK explicitly via SetGroupVersionKind() on the unstructured object ensures the correct kind is returned in update responses.


722-728: LGTM!

Creating unstructured watch events with the correct GVK ensures watch clients receive objects with the proper kind field.

packages/system/cozystack-api/cozystack-api-openssl.cnf (1)

1-13: LGTM!

The OpenSSL configuration properly defines a CSR with subject alternative name (SAN) for localhost, which is appropriate for local API server TLS setup. The configuration aligns with the Makefile's run-local and debug targets.

packages/system/cozystack-api/Makefile (1)

16-17: Update debug target certificate paths if run-local is refactored.

If the run-local target is updated to use apiserver.local.config/ directory, ensure the debug target references the same certificate paths for consistency.

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

29-33: Verify test environment resources are pre-established.

The concern about accessing items[0] without checking is technically valid—if collections are empty, jq .items[0].kind returns null, resulting in a misleading error message "got null" rather than "array is empty."

However, this is an e2e test querying live cluster APIs. Determine whether:

  • Tenants and ingresses are created by test infrastructure before this test runs
  • Empty collections are a realistic scenario or theoretical edge case

If resources are guaranteed to exist in your test environment, the review comment can be dismissed. If empty collections are possible, consider adding a pre-flight check or improving error messages.

Comment thread pkg/apiserver/apiserver.go
@lllamnyp Timofei Larkin (lllamnyp) added the kind/backport Categorizes issue or PR as requiring a backport to the current release line label Nov 13, 2025

@kvaps Andrei Kvapil (kvaps) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Nov 13, 2025

@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

♻️ Duplicate comments (4)
hack/e2e-test-openapi.bats (1)

23-44: Store kubectl output in variables to avoid redundant API calls.

The kubectl commands for tenants and ingresses are executed multiple times. Store each output in a variable and reuse it for subsequent checks to improve efficiency.

Apply this diff:

 @test "Test kinds" {
-  val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.kind')
+  tenants_raw=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants)
+  val=$(echo "$tenants_raw" | 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')
+  val=$(echo "$tenants_raw" | 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')
+  ingresses_raw=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses)
+  val=$(echo "$ingresses_raw" | 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')
+  val=$(echo "$ingresses_raw" | jq -r '.items[0].kind')
   if [ "$val" != "Ingress" ]; then
     echo "Expected kind to be Ingress, got $val"
     exit 1
   fi
 }
pkg/registry/apps/application/rest.go (2)

1068-1095: Simplify control flow with nested if statements.

The for {} loop with break statements (lines 1069-1090) is unconventional and reduces readability. Using nested if statements would make the intent clearer: attempt to parse as a list, and fall through to single-object parsing if that fails.

Apply this diff:

 	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
-			}
+		if items, ok := obj.Object["items"]; ok {
+			if objects, ok := items.([]unstructured.Unstructured); ok {
-			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 := 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)
 				}
-				apps = append(apps, a)
+				table = r.buildTableFromApplications(apps)
+				table.ListMeta.ResourceVersion = obj.GetResourceVersion()
+				break
 			}
-			break
 		}
-		if apps != nil {
-			table = r.buildTableFromApplications(apps)
-			table.ListMeta.ResourceVersion = obj.GetResourceVersion()
-			break
-		}
+		// Fall through to single-object handling
 		var app appsv1alpha1.Application
 		err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &app)

1238-1243: Fix type inconsistency in items initialization.

Line 1241 initializes items as make([]interface{}, 0), but List() (line 394) populates it with []unstructured.Unstructured, and ConvertToTable() (lines 1076-1078) type-asserts it as []unstructured.Unstructured. If NewList() is called without going through List(), the type assertion in ConvertToTable() will fail, causing a runtime panic.

Apply this diff:

 func (r *REST) NewList() runtime.Object {
 	obj := &unstructured.Unstructured{}
 	obj.SetGroupVersionKind(r.gvk.GroupVersion().WithKind(r.kindName + "List"))
-	obj.Object["items"] = make([]interface{}, 0)
+	obj.Object["items"] = make([]unstructured.Unstructured, 0)
 	return obj
 }
pkg/apiserver/apiserver.go (1)

68-81: Add corev1alpha1 types to mgrScheme for REST storage client compatibility.

The REST storage implementations for tenantsecrets, tenantnamespaces, and tenantmodules (lines 181-189) use clients built with mgrScheme (lines 145, 175). However, corev1alpha1 types are only registered to the global Scheme (line 69), not to mgrScheme. This will cause serialization/deserialization failures when these REST endpoints handle tenant resources.

Add corev1alpha1 registration to mgrScheme:

 	if err := helmv2.AddToScheme(mgrScheme); err != nil {
 		panic(fmt.Errorf("Failed to add HelmRelease types to scheme: %w", err))
 	}
 
+	if err := corev1alpha1.AddToScheme(mgrScheme); err != nil {
+		panic(fmt.Errorf("Failed to add corev1alpha1 types to scheme: %w", err))
+	}
+
 	if err := corev1.AddToScheme(mgrScheme); err != nil {
 		panic(fmt.Errorf("Failed to add core types to scheme: %w", err))
 	}

Note: You'll need to import the corev1alpha1 package:

import (
	// ... existing imports ...
	corev1alpha1 "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1"
)
🧹 Nitpick comments (1)
packages/system/cozystack-api/Makefile (1)

16-17: Align debug target build flags with run-local.

The debug target invokes dlv debug on the source directly without CGO_ENABLED=0, whereas run-local explicitly disables CGO. For consistency and to avoid potential build issues, dlv debug should use the same CGO setting.

Apply this diff to align the build flags:

 debug:
-	dlv debug ../../../cmd/cozystack-api/main.go -- --client-ca-file /tmp/ca.crt --tls-cert-file /tmp/apiserver.crt --tls-private-key-file /tmp/apiserver.key --secure-port 6443 --kubeconfig $(KUBECONFIG) --authorization-kubeconfig $(KUBECONFIG) --authentication-kubeconfig $(KUBECONFIG)
+	CGO_ENABLED=0 dlv debug ../../../cmd/cozystack-api/main.go -- --client-ca-file /tmp/ca.crt --tls-cert-file /tmp/apiserver.crt --tls-private-key-file /tmp/apiserver.key --secure-port 6443 --kubeconfig $(KUBECONFIG) --authorization-kubeconfig $(KUBECONFIG) --authentication-kubeconfig $(KUBECONFIG)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8de39f6 and 4e8c8d6.

📒 Files selected for processing (7)
  • hack/e2e-test-openapi.bats (1 hunks)
  • packages/system/cozystack-api/.gitignore (1 hunks)
  • packages/system/cozystack-api/Makefile (1 hunks)
  • packages/system/cozystack-api/cozystack-api-openssl.cnf (1 hunks)
  • pkg/apiserver/apiserver.go (5 hunks)
  • pkg/cmd/server/start.go (2 hunks)
  • pkg/registry/apps/application/rest.go (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/cmd/server/start.go
  • packages/system/cozystack-api/.gitignore
  • packages/system/cozystack-api/cozystack-api-openssl.cnf
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-05T05:50:23.971Z
Learnt from: lllamnyp
Repo: cozystack/cozystack PR: 870
File: packages/system/keycloak/templates/ingress.yaml:8-15
Timestamp: 2025-05-05T05:50:23.971Z
Learning: In Go templates for Helm charts, variables that will hold objects from resource lookups should be initialized with `dict` rather than `list` or `nil`. This is because an empty map (`dict`) provides safer behavior when checking for nested properties in conditional statements like `{{- if and $var $var.spec $var.spec.values }}`.

Applied to files:

  • pkg/registry/apps/application/rest.go
🧬 Code graph analysis (2)
pkg/registry/apps/application/rest.go (5)
pkg/apis/apps/v1alpha1/types.go (1)
  • Application (59-67)
pkg/generated/applyconfiguration/apps/v1alpha1/application.go (1)
  • Application (39-46)
pkg/registry/core/tenantmodule/rest.go (1)
  • REST (72-79)
pkg/registry/registry.go (1)
  • REST (26-28)
api/v1alpha1/groupversion_info.go (1)
  • GroupVersion (29-29)
pkg/apiserver/apiserver.go (4)
pkg/apis/apps/install/install.go (1)
  • Install (26-29)
pkg/apis/core/v1alpha1/register.go (1)
  • AddToScheme (31-31)
pkg/apis/apps/v1alpha1/register.go (1)
  • AddToScheme (32-32)
api/v1alpha1/groupversion_info.go (1)
  • AddToScheme (35-35)
🔇 Additional comments (6)
packages/system/cozystack-api/Makefile (1)

7-18: Verify the OpenSSL config file exists and working directory assumptions are met.

The run-local and debug targets reference cozystack-api-openssl.cnf with a relative path (line 11), which assumes the Makefile is executed from the packages/system/cozystack-api/ directory. Per the AI summary, this config file should exist in the same directory. Additionally, the relative path to the Go source file (../../../cmd/cozystack-api/main.go) depends on the same working directory assumption.

Please confirm:

  1. The cozystack-api-openssl.cnf file exists at packages/system/cozystack-api/cozystack-api-openssl.cnf.
  2. These targets are documented to be invoked from the packages/system/cozystack-api/ directory, or consider using $(CURDIR) or similar to make paths more robust.
pkg/apiserver/apiserver.go (2)

63-67: LGTM: Logger setup for local development.

The Zap logger configuration with development mode and klog integration is appropriate for the local API server development targets mentioned in the PR objectives.


144-189: LGTM: Manager and watch client configuration.

The manager and watch client are correctly configured with mgrScheme, and REST storage properly receives these clients. Once the corev1alpha1 registration issue is addressed, this implementation will correctly support both static and dynamic resources.

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

1231-1235: LGTM: Unstructured-based New() implementation.

Returning *unstructured.Unstructured with SetGroupVersionKind correctly addresses the PR's objective of fixing list kind representation for dynamic resources. This allows the runtime scheme to properly infer the kind.


392-397: LGTM: List() correctly builds unstructured list response.

The implementation properly calls NewList(), assigns the strongly-typed items, and sets the resource version. Once the type inconsistency in NewList() is addressed, this will work correctly.


442-454: LGTM: Update() correctly handles unstructured input and output.

The method properly converts the incoming *unstructured.Unstructured to an Application for processing, then returns an unstructured object with the correct GVK. This maintains API consistency with the new unstructured-based approach.

Also applies to: 524-529

## What this PR does

This patch fixes an issue with the Cozystack API server that causes it
to respond with the first registered dynamic list kind when listing
dynamic resources. E.g., when running `k get postgreses`, the raw JSON
response from the cozystack API would be

```json
{
  "apiVersion":"apps.cozystack.io/v1alpha1",
  "kind":"BootBoxList",
  "items": [
    {
      "apiVersion":"apps.cozystack.io/v1alpha1",
      "kind":"Postgres",
      ...
    },
    ...
  ],
  ...
}
```

The root cause is the way the `Typer` interface is implemented for the
`runtime.Scheme` where the dynamic types are registered. Since the base
type for all dynamic types is a `&cozyv1alpha1.Application{}`, all types
are registered under the same key and the `Typer` defaults to the first
`GroupVersionKind` that was registered. Only when a correctly formed
`&unstructured.Unstructured{}` is returned by the API, is this resolving
logic circumvented and the `GroupVersionKind` is instead inferred from
the fields of the returned object. Even an `UnstructuredList` is not
acceptable as a return type, instead the `items` key should be directly
set on the underlying `map[string]interface{}`.

This patch implements the changes detailed above. Additionally, the
following features, fixes, and improvements are added:

* Makefile targets to build and run the Cozystack API locally, against a
  Kubernetes server in the environment's KUBECONFIG. Debugging with
  Delve is also supported.
* CI tests are added to verify the new changes.
* A bug in the registration of the corev1alpha1 types is fixed.
* Updated the `ConvertToTable` function to properly handle list kinds
  which might be of the `&unstructured.Unstructured{}` concrete type
  (not an `UnstructuredList`).
* The scheme used by the API server's Client and WatchClient is
  separated from the scheme used to serve dynamic types.
* The client config for reading the runtime configuration now uses the
  controller-runtime, which handles flags and environment variables
  properly, unlike `clientcmd`.

### Release note

```release-note
[api] Fix incorrect list kind for list requests to the Cozystack API for
dynamic resources. Add Makefile targets for local testing. Minor schema
building improvements.
```

Signed-off-by: Timofei Larkin <[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: 2

♻️ Duplicate comments (2)
hack/e2e-test-openapi.bats (1)

23-44: Cache kubectl results and validate items array.

The past review correctly identified that kubectl get --raw is executed multiple times for the same resource, which is inefficient. Additionally, accessing .items[0] without verifying the array is non-empty could cause test failures if no resources exist.

Consider this refactored approach:

 @test "Test kinds" {
+  # Cache API responses
+  tenants_raw=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants)
+  ingresses_raw=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses)
+  
+  # Validate tenants
-  val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.kind')
+  val=$(echo "$tenants_raw" | 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')
+  val=$(echo "$tenants_raw" | jq -r '.items[0].kind // empty')
   if [ "$val" != "Tenant" ]; then
-    echo "Expected kind to be Tenant, got $val"
+    echo "Expected kind to be Tenant, got $val (or no items found)"
     exit 1
   fi
+  
+  # Validate ingresses
-  val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses | jq -r '.kind')
+  val=$(echo "$ingresses_raw" | 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')
+  val=$(echo "$ingresses_raw" | jq -r '.items[0].kind // empty')
   if [ "$val" != "Ingress" ]; then
-    echo "Expected kind to be Ingress, got $val"
+    echo "Expected kind to be Ingress, got $val (or no items found)"
     exit 1
   fi
 }

The // empty operator in jq returns an empty string if .items[0] doesn't exist, preventing jq errors.

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

1247-1250: Address the type inconsistency in items initialization.

The items field is initialized as []interface{}{}, but the List method (line 402) populates it with []unstructured.Unstructured, and ConvertToTable (line 1084) performs a type assertion expecting []unstructured.Unstructured. If an unpopulated list created by NewList() reaches ConvertToTable, the type assertion will fail, causing the code to fall through to the single-object case and likely fail.

Apply this fix as suggested in past reviews:

-	obj.Object["items"] = make([]interface{}, 0)
+	obj.Object["items"] = make([]unstructured.Unstructured, 0)

Based on learnings

🧹 Nitpick comments (4)
packages/system/cozystack-api/Makefile (1)

16-17: Document dlv debugger requirement and consider build optimizations for debugging.

The debug target invokes dlv debug but does not verify that Delve is installed. Additionally, for optimal debugging, consider adding explicit debug symbols (-gcflags="all=-N -l").

  1. Document in a README or comment that dlv (Delve debugger) must be installed: go install github.com/go-delve/delve/cmd/dlv@latest.
  2. Consider adding debug symbols to the debug target for better debugging experience:
 debug:
-	dlv debug ../../../cmd/cozystack-api/main.go -- --client-ca-file /tmp/ca.crt --tls-cert-file /tmp/apiserver.crt --tls-private-key-file /tmp/apiserver.key --secure-port 6443 --kubeconfig $(KUBECONFIG) --authorization-kubeconfig $(KUBECONFIG) --authentication-kubeconfig $(KUBECONFIG)
+	dlv debug -gcflags="all=-N -l" ../../../cmd/cozystack-api/main.go -- --client-ca-file /tmp/ca.crt --tls-cert-file /tmp/apiserver.crt --tls-private-key-file /tmp/apiserver.key --secure-port 6443 --kubeconfig $(KUBECONFIG) --authorization-kubeconfig $(KUBECONFIG) --authentication-kubeconfig $(KUBECONFIG)

However, verify that dlv actually accepts -gcflags in this context; alternatively, the flags may need to be passed differently.

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

145-156: Consider clarifying the error message.

The error message on line 153 states "returned unstructured.Unstructured object was not an Application," but the actual issue is that the conversion from unstructured to Application failed. The object was received as input (via the obj parameter), not returned.

Consider this revision:

 	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(us.Object, app); err != nil {
-		errMsg := fmt.Sprintf("returned unstructured.Unstructured object was not an Application")
+		errMsg := fmt.Sprintf("failed to convert unstructured object to Application: %v", err)
 		klog.Errorf(errMsg)
-		return nil, fmt.Errorf(errMsg)
+		return nil, fmt.Errorf("%s", errMsg)
 	}

450-462: Consider clarifying the error message.

Similar to the Create method, the error message on line 459 could be more precise about the actual failure (conversion error) rather than stating "returned unstructured.Unstructured object was not an Application."

Apply this diff:

 	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(us.Object, app); err != nil {
-		errMsg := fmt.Sprintf("returned unstructured.Unstructured object was not an Application")
+		errMsg := fmt.Sprintf("failed to convert unstructured object to Application: %v", err)
 		klog.Errorf(errMsg)
 		return nil, false, fmt.Errorf(errMsg)
 	}

1076-1103: Consider refactoring the control flow for clarity.

The for {} loop with breaks is being used for early-exit control flow, making the code harder to follow. Nested if statements would express the intent more clearly.

Apply the refactor suggested in past reviews:

-	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 {
+	if items, ok := obj.Object["items"]; ok {
+		if objects, ok := items.([]unstructured.Unstructured); ok {
+			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)
+			}
+			table = r.buildTableFromApplications(apps)
+			table.ListMeta.ResourceVersion = obj.GetResourceVersion()
+			break
+		}
+	}
+ 	// Fall through to single object case
-		table = r.buildTableFromApplications(apps)
-		table.ListMeta.ResourceVersion = obj.GetResourceVersion()
-		break
-	}
 	var app appsv1alpha1.Application

Based on learnings

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e8c8d6 and 5414d37.

📒 Files selected for processing (7)
  • hack/e2e-test-openapi.bats (1 hunks)
  • packages/system/cozystack-api/.gitignore (1 hunks)
  • packages/system/cozystack-api/Makefile (1 hunks)
  • packages/system/cozystack-api/cozystack-api-openssl.cnf (1 hunks)
  • pkg/apiserver/apiserver.go (5 hunks)
  • pkg/cmd/server/start.go (2 hunks)
  • pkg/registry/apps/application/rest.go (7 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/system/cozystack-api/.gitignore
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/system/cozystack-api/cozystack-api-openssl.cnf
  • pkg/cmd/server/start.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-05T05:50:23.971Z
Learnt from: lllamnyp
Repo: cozystack/cozystack PR: 870
File: packages/system/keycloak/templates/ingress.yaml:8-15
Timestamp: 2025-05-05T05:50:23.971Z
Learning: In Go templates for Helm charts, variables that will hold objects from resource lookups should be initialized with `dict` rather than `list` or `nil`. This is because an empty map (`dict`) provides safer behavior when checking for nested properties in conditional statements like `{{- if and $var $var.spec $var.spec.values }}`.

Applied to files:

  • pkg/registry/apps/application/rest.go
🧬 Code graph analysis (2)
pkg/apiserver/apiserver.go (4)
pkg/apis/apps/install/install.go (1)
  • Install (26-29)
pkg/apis/core/v1alpha1/register.go (1)
  • AddToScheme (31-31)
pkg/apis/apps/v1alpha1/register.go (1)
  • AddToScheme (32-32)
api/v1alpha1/groupversion_info.go (1)
  • AddToScheme (35-35)
pkg/registry/apps/application/rest.go (4)
pkg/apis/apps/v1alpha1/types.go (1)
  • Application (59-67)
pkg/generated/applyconfiguration/apps/v1alpha1/application.go (1)
  • Application (39-46)
pkg/registry/core/tenantmodule/rest.go (1)
  • REST (72-79)
api/v1alpha1/groupversion_info.go (1)
  • GroupVersion (29-29)
⏰ 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 (10)
pkg/apiserver/apiserver.go (6)

33-33: LGTM: Logger imports added.

The klog and zap imports support the logging setup introduced in the init() function.

Also applies to: 37-37


42-42: LGTM: Import path corrected.

The import path now correctly references pkg/apis/core/install instead of apps/install.


53-54: LGTM: Separate scheme for manager introduced.

The introduction of mgrScheme aligns with the PR objective to separate the scheme used by the API server's Client/WatchClient from the scheme serving dynamic types.


63-67: LGTM: Logging setup added.

The structured logging setup using Zap and bridging klog is a standard pattern for controller-runtime projects.


145-145: LGTM: Manager uses mgrScheme.

The manager correctly uses mgrScheme, which will contain all necessary types once the critical issue in lines 68-81 is resolved.


175-175: LGTM: Watch client uses mgrScheme.

The watch client correctly uses mgrScheme, matching the manager's scheme. This will work correctly once the critical issue in lines 68-81 is resolved.

packages/system/cozystack-api/Makefile (1)

11-12: Verify that cozystack-api-openssl.cnf is committed to git.

The config file exists at packages/system/cozystack-api/cozystack-api-openssl.cnf with valid OpenSSL content, and the local .gitignore correctly does not ignore it (contains only apiserver.local.config/), allowing it to be tracked. However, please manually verify that this config file is committed to git and accessible to developers cloning the repository.

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

532-537: LGTM!

The use of SetGroupVersionKind correctly sets the GVK metadata on the unstructured object, which aligns with the PR's goal of fixing list kind representation.


737-743: LGTM!

The watch event objects correctly use SetGroupVersionKind to set the appropriate GVK, ensuring watch responses have the correct kind.


1240-1242: LGTM!

The New() method correctly returns an unstructured object with the appropriate GroupVersionKind set, which is essential for the API server to properly identify the resource type.

Comment on lines +7 to +14
run-local:
openssl req -nodes -new -x509 -keyout /tmp/ca.key -out /tmp/ca.crt -subj "/CN=kube-ca"
openssl req -out /tmp/client.csr -new -newkey rsa:2048 -nodes -keyout /tmp/client.key -subj "/C=US/ST=SomeState/L=L/OU=Dev/CN=development/O=system:masters"
openssl x509 -req -days 365 -in /tmp/client.csr -CA /tmp/ca.crt -CAkey /tmp/ca.key -set_serial 01 -sha256 -out /tmp/client.crt
openssl req -out /tmp/apiserver.csr -new -newkey rsa:2048 -nodes -keyout /tmp/apiserver.key -subj "/CN=cozystack-api" -config cozystack-api-openssl.cnf
openssl x509 -req -days 365 -in /tmp/apiserver.csr -CA /tmp/ca.crt -CAkey /tmp/ca.key -set_serial 01 -sha256 -out /tmp/apiserver.crt -extensions v3_req -extfile cozystack-api-openssl.cnf
CGO_ENABLED=0 go build -o /tmp/cozystack-api ../../../cmd/cozystack-api/main.go
/tmp/cozystack-api --client-ca-file /tmp/ca.crt --tls-cert-file /tmp/apiserver.crt --tls-private-key-file /tmp/apiserver.key --secure-port 6443 --kubeconfig $(KUBECONFIG) --authorization-kubeconfig $(KUBECONFIG) --authentication-kubeconfig $(KUBECONFIG)

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Ensure environment dependencies and config files are documented.

The run-local target relies on external tools (openssl) and a configuration file (cozystack-api-openssl.cnf) that must exist relative to the Makefile. Additionally, $(KUBECONFIG) is used directly; if the environment variable is not set, the flags will be invalid.

  • Verify that cozystack-api-openssl.cnf exists at the expected location: packages/system/cozystack-api/cozystack-api-openssl.cnf.
  • Verify that cmd/cozystack-api/main.go exists at the path resolved from the relative path ../../../cmd/cozystack-api/main.go.
  • Confirm that KUBECONFIG is always set in the developer environment, or add a check/error message if not.

Run the following to verify file paths:


🏁 Script executed:

#!/bin/bash
# Description: Verify that dependency files exist and are correctly referenced.

# Check if OpenSSL config file exists
if [ -f packages/system/cozystack-api/cozystack-api-openssl.cnf ]; then
  echo "✓ openssl config file found"
else
  echo "✗ openssl config file NOT found at packages/system/cozystack-api/cozystack-api-openssl.cnf"
fi

# Check if main.go exists (relative path from Makefile: ../../../cmd/cozystack-api/main.go)
if [ -f cmd/cozystack-api/main.go ]; then
  echo "✓ main.go found at cmd/cozystack-api/main.go"
else
  echo "✗ main.go NOT found at cmd/cozystack-api/main.go"
fi

# Check for KUBECONFIG in current shell
if [ -z "$KUBECONFIG" ]; then
  echo "⚠ KUBECONFIG is not set (may cause issues at runtime)"
else
  echo "✓ KUBECONFIG is set to: $KUBECONFIG"
fi

Length of output: 126


I need to verify the Makefile content more thoroughly to check if there's any KUBECONFIG validation and to see the debug target as well.


🏁 Script executed:

#!/bin/bash
# Verify KUBECONFIG handling in Makefile and check for documentation

# View the full Makefile
echo "=== Full Makefile content ==="
cat packages/system/cozystack-api/Makefile

# Search for any KUBECONFIG validation or checks in Makefile
echo ""
echo "=== Searching for KUBECONFIG checks in Makefile ==="
grep -n "KUBECONFIG" packages/system/cozystack-api/Makefile

# Look for README or setup documentation
echo ""
echo "=== Checking for documentation files ==="
find packages/system/cozystack-api -type f \( -name "README*" -o -name "*.md" -o -name "SETUP*" \) -exec basename {} \;

Length of output: 2697


Let me check the included common makefiles to see if KUBECONFIG is defined there:


🏁 Script executed:

#!/bin/bash
# Check if KUBECONFIG is set in common makefiles

echo "=== Checking ../../../scripts/common-envs.mk ==="
if [ -f scripts/common-envs.mk ]; then
  grep -n "KUBECONFIG" scripts/common-envs.mk || echo "(KUBECONFIG not found in common-envs.mk)"
else
  echo "File not found"
fi

echo ""
echo "=== Checking ../../../scripts/package.mk ==="
if [ -f scripts/package.mk ]; then
  grep -n "KUBECONFIG" scripts/package.mk || echo "(KUBECONFIG not found in package.mk)"
else
  echo "File not found"
fi

# Also search project root for KUBECONFIG setup hints
echo ""
echo "=== Searching for KUBECONFIG in README or setup docs ==="
find . -maxdepth 2 -type f \( -name "README*" -o -name "*.md" \) -exec grep -l "KUBECONFIG" {} \;

Length of output: 230


Add validation for KUBECONFIG environment variable to prevent silent failures.

Verification confirms that while cozystack-api-openssl.cnf and cmd/cozystack-api/main.go exist at the expected locations, the run-local and debug targets both use $(KUBECONFIG) without validation. If KUBECONFIG is not set in the environment, the flags will expand to empty values (e.g., --kubeconfig), causing the API server to fail or behave unexpectedly. The variable is not defined in scripts/common-envs.mk or scripts/package.mk, and no documentation exists to guide developers on this requirement.

Add a check at the beginning of both run-local and debug targets to validate that KUBECONFIG is set, or provide a clear error message:

run-local:
	@[ -n "$(KUBECONFIG)" ] || (echo "Error: KUBECONFIG environment variable not set"; exit 1)
	openssl req -nodes -new -x509 -keyout /tmp/ca.key -out /tmp/ca.crt -subj "/CN=kube-ca"
	...

Apply the same check to the debug target.

🤖 Prompt for AI Agents
In packages/system/cozystack-api/Makefile around lines 7 to 14, the run-local
target (and likewise the debug target) use $(KUBECONFIG) without validating it
which can expand to empty flags and cause silent failures; add a guard at the
start of both targets that checks KUBECONFIG is non-empty and prints a clear
error and exits if not (for example using a shell conditional that echoes
"Error: KUBECONFIG environment variable not set" and exits 1) so the Makefile
fails fast and informs the developer.

Comment on lines 68 to 81
appsinstall.Install(Scheme)
coreinstall.Install(Scheme)

// Register HelmRelease types.
if err := helmv2.AddToScheme(Scheme); err != nil {
if err := helmv2.AddToScheme(mgrScheme); err != nil {
panic(fmt.Errorf("Failed to add HelmRelease types to scheme: %w", err))
}

if err := corev1.AddToScheme(Scheme); err != nil {
if err := corev1.AddToScheme(mgrScheme); err != nil {
panic(fmt.Errorf("Failed to add core types to scheme: %w", err))
}
if err := rbacv1.AddToScheme(Scheme); err != nil {
if err := rbacv1.AddToScheme(mgrScheme); err != nil {
panic(fmt.Errorf("Failed to add RBAC types to scheme: %w", err))
}

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.

⚠️ Potential issue | 🔴 Critical

Critical: Register appsv1alpha1 and corev1alpha1 types to mgrScheme.

Both appsv1alpha1 (line 68) and corev1alpha1 (line 69) types are registered only to Scheme, not to mgrScheme. However, the REST storage implementations for both core resources (lines 181-189) and apps resources (line 200) use cli and watchCli, which are built with mgrScheme (lines 145, 175). Without these type registrations in mgrScheme, the clients will fail to serialize/deserialize tenant and application resources.

Add the following registrations to mgrScheme after line 71:

 	// Register HelmRelease types.
+	if err := appsv1alpha1.AddToScheme(mgrScheme); err != nil {
+		panic(fmt.Errorf("Failed to add apps types to scheme: %w", err))
+	}
+	if err := corev1alpha1.AddToScheme(mgrScheme); err != nil {
+		panic(fmt.Errorf("Failed to add core types to scheme: %w", err))
+	}
 	if err := helmv2.AddToScheme(mgrScheme); err != nil {

Ensure the packages are imported:

appsv1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1"
corev1alpha1 "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1"
🤖 Prompt for AI Agents
In pkg/apiserver/apiserver.go around lines 68 to 81, appsv1alpha1 and
corev1alpha1 types were only registered to Scheme but not to mgrScheme which
breaks serialization for clients built with mgrScheme; register both
appsv1alpha1.AddToScheme(mgrScheme) and corev1alpha1.AddToScheme(mgrScheme)
immediately after line 71, and ensure the files import appsv1alpha1
"github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1" and corev1alpha1
"github.com/cozystack/cozystack/pkg/apis/core/v1alpha1".

@lllamnyp
Timofei Larkin (lllamnyp) merged commit 9c040cd into main Nov 13, 2025
22 checks passed
@lllamnyp
Timofei Larkin (lllamnyp) deleted the fix/cozy-api-typing branch November 13, 2025 15:52
@github-actions

Copy link
Copy Markdown

Successfully created backport PR for release-0.37:

Andrei Kvapil (kvaps) added a commit that referenced this pull request Dec 1, 2025
…e deletion regression from #1630) (#1677)

## Problem

This PR reverts the changes introduced in #1630 that caused a regression
affecting namespace deletion and upgrades from previous versions,
particularly when running migration 20.

### Symptoms

Users are experiencing issues with namespace deletion and upgrades. The
namespace controller reports:

```
NamespaceDeletionContentFailure: True
ContentDeletionFailed: Failed to delete all resource types, 25 remaining: 
content is not a list: []unstructured.Unstructured
```

The error indicates that Kubernetes API server expects a proper list
type, but receives `[]unstructured.Unstructured` instead, which breaks
namespace finalization.

### Root Cause

The changes in #1630 modified how dynamic list kinds are represented,
switching from `unstructured.UnstructuredList` to typed
`ApplicationList`. While this was intended to fix kind representation
issues, it introduced a compatibility problem:

1. The namespace deletion controller expects resources to implement the
standard Kubernetes list interface
2. The typed lists may not be properly recognized during namespace
finalization
3. This breaks upgrades from previous versions where resources were
stored as unstructured

### Solution

This PR reverts the problematic changes to restore compatibility with
namespace deletion and upgrade scenarios. We are planning a more
comprehensive fix that will address both the original kind
representation issue and namespace deletion compatibility.

### Related Issues

- Fixes namespace deletion failures
- Fixes upgrade issues from previous versions (migration 20)
- Reverts regression introduced in #1630

### Next Steps

A more comprehensive fix is being prepared that will:
- Properly handle kind representation for dynamic types
- Ensure compatibility with namespace deletion controller
- Support upgrades from previous versions
Andrei Kvapil (kvaps) added a commit that referenced this pull request Dec 1, 2025
…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]>
Andrei Kvapil (kvaps) added a commit that referenced this pull request Dec 1, 2025
…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]>
Andrei Kvapil (kvaps) added a commit that referenced this pull request Dec 1, 2025
…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]>
Andrei Kvapil (kvaps) added a commit that referenced this pull request Dec 1, 2025
…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]>
Andrei Kvapil (kvaps) added a commit that referenced this pull request Dec 10, 2025
…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 -->
github-actions Bot pushed a commit that referenced this pull request Dec 10, 2025
…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]>
(cherry picked from commit ca29fc8)
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 -->
Nikita (nbykov0) pushed a commit that referenced this pull request Jan 9, 2026
…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]>
(cherry picked from commit ca29fc8)
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.

2 participants