Repository navigation
[api] Fix representation of dynamic list kinds - #1630
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughAdds 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
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)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
Summary of ChangesHello 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
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)| @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 | ||
| } |
There was a problem hiding this comment.
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
}
| 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 | ||
| } |
There was a problem hiding this comment.
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
}
}There was a problem hiding this comment.
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.Unstructuredthat 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-localtarget generates new certificates each time it's invoked. Consider these improvements:
- Certificate cleanup: Add a cleanup mechanism or use the ignored
apiserver.local.config/directory instead of/tmpfor better organization.- 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
📒 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
.gitignoreentry appropriately excludes local development artifacts generated by the newrun-localanddebugMakefile 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) fromScheme(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.Unstructuredwith 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.Unstructuredwith 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 settingObject["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-localanddebugtargets.packages/system/cozystack-api/Makefile (1)
16-17: Update debug target certificate paths if run-local is refactored.If the
run-localtarget is updated to useapiserver.local.config/directory, ensure thedebugtarget 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].kindreturnsnull, 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.
8de39f6 to
4e8c8d6
Compare
There was a problem hiding this comment.
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 withbreakstatements (lines 1069-1090) is unconventional and reduces readability. Using nestedifstatements 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
itemsasmake([]interface{}, 0), butList()(line 394) populates it with[]unstructured.Unstructured, andConvertToTable()(lines 1076-1078) type-asserts it as[]unstructured.Unstructured. IfNewList()is called without going throughList(), the type assertion inConvertToTable()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, andtenantmodules(lines 181-189) use clients built withmgrScheme(lines 145, 175). However,corev1alpha1types are only registered to the globalScheme(line 69), not tomgrScheme. 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
debugtarget invokesdlv debugon the source directly withoutCGO_ENABLED=0, whereasrun-localexplicitly disables CGO. For consistency and to avoid potential build issues,dlv debugshould 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
📒 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-localanddebugtargets referencecozystack-api-openssl.cnfwith a relative path (line 11), which assumes the Makefile is executed from thepackages/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:
- The
cozystack-api-openssl.cnffile exists atpackages/system/cozystack-api/cozystack-api-openssl.cnf.- 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.UnstructuredwithSetGroupVersionKindcorrectly 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 inNewList()is addressed, this will work correctly.
442-454: LGTM: Update() correctly handles unstructured input and output.The method properly converts the incoming
*unstructured.Unstructuredto anApplicationfor 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]>
4e8c8d6 to
5414d37
Compare
There was a problem hiding this comment.
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 --rawis 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
// emptyoperator 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
itemsfield is initialized as[]interface{}{}, but theListmethod (line 402) populates it with[]unstructured.Unstructured, andConvertToTable(line 1084) performs a type assertion expecting[]unstructured.Unstructured. If an unpopulated list created byNewList()reachesConvertToTable, 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
debugtarget invokesdlv debugbut does not verify that Delve is installed. Additionally, for optimal debugging, consider adding explicit debug symbols (-gcflags="all=-N -l").
- Document in a README or comment that
dlv(Delve debugger) must be installed:go install github.com/go-delve/delve/cmd/dlv@latest.- 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
-gcflagsin 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
objparameter), 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. Nestedifstatements 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.ApplicationBased on learnings
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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
klogandzapimports support the logging setup introduced in theinit()function.Also applies to: 37-37
42-42: LGTM: Import path corrected.The import path now correctly references
pkg/apis/core/installinstead ofapps/install.
53-54: LGTM: Separate scheme for manager introduced.The introduction of
mgrSchemealigns 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 thatcozystack-api-openssl.cnfis committed to git.The config file exists at
packages/system/cozystack-api/cozystack-api-openssl.cnfwith valid OpenSSL content, and the local.gitignorecorrectly does not ignore it (contains onlyapiserver.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
SetGroupVersionKindcorrectly 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
SetGroupVersionKindto 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.
| 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) |
There was a problem hiding this comment.
🧩 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.cnfexists at the expected location:packages/system/cozystack-api/cozystack-api-openssl.cnf. - Verify that
cmd/cozystack-api/main.goexists at the path resolved from the relative path../../../cmd/cozystack-api/main.go. - Confirm that
KUBECONFIGis 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"
fiLength 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.
| 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)) | ||
| } |
There was a problem hiding this comment.
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".
|
Successfully created backport PR for |
…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
…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]>
…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]>
…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]>
…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]>
…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 -->
…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)
…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 -->
…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 -->
…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)
…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 -->
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
Typerinterface is implemented for theruntime.Schemewhere 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 theTyperdefaults to the firstGroupVersionKindthat was registered. Only when a correctly formed&unstructured.Unstructured{}is returned by the API, is this resolving logic circumvented and theGroupVersionKindis instead inferred from the fields of the returned object. Even anUnstructuredListis not acceptable as a return type, instead theitemskey should be directly set on the underlyingmap[string]interface{}.This patch implements the changes detailed above. Additionally, the following features, fixes, and improvements are added:
ConvertToTablefunction to properly handle list kinds which might be of the&unstructured.Unstructured{}concrete type (not anUnstructuredList).clientcmd.Release note
Summary by CodeRabbit
Tests
Chores
Refactor