Compare commits

...
Author SHA1 Message Date
hanzo-dev aea0a2c86d fix(deploy): list AppProjects from the group the cluster serves
/v1/deploy/projects asked the API server for appprojects at group argoproj.io.
Nothing has ever served that group here — the request comes back "the server
doesn't have a resource type", and listAppProjects treats any error as "the CRD
is absent", so it silently synthesized a fake project set instead.

The cluster serves appprojects.apps.hanzo.ai, and it has real ones (default,
hanzo). So the fallback was not covering for a missing CRD; it was covering for
asking the wrong question, and the operator's actual policy envelopes never
appeared on the dashboard.

The comment above the GVR asserted "this plane does not run argocd, so the CRD
is normally absent." That was true once. It stopped being true when Hanzo CD
was installed, and the code kept believing it.

CDApplications already named apps.hanzo.ai correctly one file over; the project
GVR was simply declared privately in projection.go and missed. Moved to
clients/k8s beside its sibling, which is where GVRs are supposed to live so the
next one cannot drift alone.

Unchanged on purpose: the argoproj.io/v1alpha1 apiVersion strings the projection
EMITS. Those are response shape the cd-ui SPA consumes, not a query, so they
need the UI checked before they move.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-27 23:41:03 -07:00
4 changed files with 16 additions and 10 deletions
+2 -1
View File
@@ -32,6 +32,7 @@ import (
"time"
"github.com/hanzoai/cloud"
"github.com/hanzoai/cloud/clients/k8s"
"github.com/zap-proto/zip"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -160,7 +161,7 @@ func dashProjects(s *cloud.Service[state], c *zip.Ctx) error {
// exists; any error (CRD absent — the norm here, or RBAC) or an empty set yields
// (nil, false) so the caller synthesizes. It never fails the request.
func listAppProjects(s *cloud.Service[state], ctx context.Context) ([]argoProject, bool) {
list, err := s.State.dyn.Resource(appProjectGVR).List(ctx, metav1.ListOptions{})
list, err := s.State.dyn.Resource(k8s.CDAppProjects).List(ctx, metav1.ListOptions{})
if err != nil || list == nil || len(list.Items) == 0 {
return nil, false
}
+3 -3
View File
@@ -29,7 +29,7 @@ func fakeService(objs ...runtime.Object) *cloud.Service[state] {
hpaGVR: "HorizontalPodAutoscalerList",
pdbGVR: "PodDisruptionBudgetList",
configMapsGVR: "ConfigMapList",
appProjectGVR: "AppProjectList",
k8s.CDAppProjects: "AppProjectList",
middlewaresGVR: "MiddlewareList",
ingressRoutesGVR: "IngressRouteList",
k8s.CDApplications: "ApplicationList",
@@ -105,8 +105,8 @@ func appProjectCR(name string, sourceRepos ...string) *unstructured.Unstructured
repos = append(repos, r)
}
return &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "argoproj.io/v1alpha1", "kind": "AppProject",
"metadata": map[string]any{"name": name, "namespace": "argocd"},
"apiVersion": k8s.CDAppProjects.GroupVersion().String(), "kind": "AppProject",
"metadata": map[string]any{"name": name, "namespace": "hanzo-cd"},
"spec": map[string]any{
"sourceRepos": repos,
"destinations": []any{map[string]any{"server": inClusterServer, "namespace": "hanzo"}},
-6
View File
@@ -16,7 +16,6 @@ package deploy
import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// ── ArgoCD v1alpha1 JSON (minimal, UI-render-complete) ───────────────────────
@@ -347,11 +346,6 @@ func projectClusters(crs []unstructured.Unstructured) argoClusterList {
// ── ArgoCD AppProject projection (distinct App-CR projects → AppProjectList) ──
// appProjectGVR is the ArgoCD AppProject CRD. This plane does not run argocd, so
// the CRD is normally absent — dashProjects checks for it and falls back to
// synthesizing a project set from the distinct App-CR project names.
var appProjectGVR = schema.GroupVersionResource{Group: "argoproj.io", Version: "v1alpha1", Resource: "appprojects"}
// argoGroupKind is metav1.GroupKind — a clusterResourceWhitelist entry.
type argoGroupKind struct {
Group string `json:"group"`
+11
View File
@@ -42,3 +42,14 @@ var Volumes = schema.GroupVersionResource{Version: "v1", Resource: "persistentvo
// WRITES those Apps, so "the App CR declares vX" and "CD has applied commit abc"
// answer different questions and neither implies the other.
var CDApplications = schema.GroupVersionResource{Group: "apps.hanzo.ai", Version: "v1alpha1", Resource: "applications"}
// CDAppProjects is Hanzo CD's AppProject CR (apps.hanzo.ai/v1alpha1) — the policy
// envelope a CD Application is admitted under: which repos it may pull from, which
// destinations it may write to, which resource kinds it may create. It answers a
// different question from CDApplications: an Application is ONE tracked git source,
// a project is the boundary a whole set of them is allowed to act within.
//
// Same group as every other CD kind. The cluster has never served argoproj.io — a
// GVR naming that group resolves to "the server doesn't have a resource type", which
// a caller that treats any error as "CD is not installed" will read as absence.
var CDAppProjects = schema.GroupVersionResource{Group: "apps.hanzo.ai", Version: "v1alpha1", Resource: "appprojects"}