Skip to content

feat(server): cross-layer children graph — Phase 6 of the deployment model#128

Merged
astrojerms merged 1 commit into
mainfrom
feat/k8s-graph
Jul 12, 2026
Merged

feat(server): cross-layer children graph — Phase 6 of the deployment model#128
astrojerms merged 1 commit into
mainfrom
feat/k8s-graph

Conversation

@astrojerms

Copy link
Copy Markdown
Owner

Final phase of docs/deployment-model.md: the unified cross-layer graph — the payoff that makes openctl a control plane no cluster-scoped tool (ArgoCD) can match.

The finding: it's all backend

The UI graph renderer (DagView.svelte) is already fully kind-agnostic — it draws any nodes/edges the backend produces. So Phase 6 is entirely in GetChildrenGraph, which had two limits:

  1. It collected $ref edges only from child specs, never the root's own spec — so a HelmRelease→Cluster edge never appeared.
  2. It was depth-1 (single pass) — so it never walked Cluster→nodes→VMs under a workload.

The change

GetChildrenGraph now:

  • collects $ref edges from the root resource's own spec (a HelmRelease's kubeconfigPath: {$ref: Cluster/…} → HelmRelease→Cluster edge), and
  • walks the composition + $ref graph breadth-first with a visited set + depth cap (the Cluster then expands via its Planner into K3sNodes/VMs, recursively).

Querying a HelmRelease now spans HelmRelease → Cluster → K3sNodes/VMs in one graph — rendered by the existing UI with zero frontend changes.

Tests

  • Refactored the single-pass expansion into expandNode(ctx, queue, work) over a BFS queue; graphBuilder gains an expanded cycle guard.
  • The test fakePlanner now only composes for kind Cluster (like the real k3s provider), so recursion treats VMs/Nodes as leaves.
  • New TestGetChildrenGraphSpansCrossLayer: seeds a HelmRelease manifest whose kubeconfigPath $refs a Cluster and asserts the multi-level span (HelmRelease→Cluster ref edge, Cluster→VM owns edge, deep Node→VM ref, 6 nodes). Existing depth-1 graph tests still pass.

Local CI

gofmt / vet / staticcheck / golangci-lint / modernize / go test / go test -race (server) all green.

Follow-ons (noted, not blocking)

  • VM→Proxmox-host edge (the k3s VM manifest doesn't $ref its ProxmoxNode yet).
  • Upward-into-pods (a ChildrenOf on plugins/k8s).

With this, deployment-model Phases 1–6 are all shipped.

…model

Makes the composition/children graph span layers so a workload query walks
down to the metal — the unified cross-layer view a cluster-scoped tool like
ArgoCD structurally can't show. The UI graph renderer (DagView.svelte) is
already fully kind-agnostic, so this is entirely backend.

GetChildrenGraph now:
- collects $ref edges from the ROOT resource's own spec (previously only
  from child specs), so a HelmRelease's kubeconfigPath {$ref: Cluster/…}
  produces a HelmRelease→Cluster edge; and
- walks the composition + $ref graph breadth-first with a visited set +
  depth cap (previously a single depth-1 pass), so the Cluster then expands
  via its Planner into K3sNodes/VMs, and so on.

Querying a HelmRelease now spans HelmRelease → Cluster → K3sNodes/VMs in one
graph, rendered by the existing UI with zero frontend changes.

- Refactored the single-pass expansion into expandNode(ctx, queue, work)
  driven by a BFS queue; graphBuilder gains an `expanded` cycle guard.
- Test fakePlanner now only composes for kind "Cluster" (like the real k3s
  provider), so recursion treats VMs/Nodes as leaves.
- New TestGetChildrenGraphSpansCrossLayer seeds a HelmRelease manifest whose
  kubeconfigPath $refs a Cluster and asserts the multi-level span
  (HelmRelease→Cluster ref edge, Cluster→VM owns edge, deep Node→VM ref,
  6 nodes). Existing depth-1 graph tests still pass.

Follow-ons: VM→Proxmox-host edge (the k3s VM manifest doesn't $ref its
ProxmoxNode yet) and upward-into-pods (a ChildrenOf on plugins/k8s).
@astrojerms astrojerms merged commit 4d14eb5 into main Jul 12, 2026
6 checks passed
astrojerms added a commit that referenced this pull request Jul 12, 2026
…aph (#130)

Adds a ChildrenOf capability to the k8s plugin so a HelmRelease or Manifest
hangs its in-cluster objects (Deployments, Services, ConfigMaps, …) under it in
the openctl children graph — the upward-into-workloads view.

Reaching the cluster to enumerate a release's objects needs the kubeconfig,
which lives only in the resource's state blob. The childrenOf request
(RefParams) previously carried only {kind, name}, so a stateful provider
couldn't answer. This threads state through:

  - pkg/pluginproto: RefParams gains an optional State field (omitempty,
    backward-compatible — the wire path is symmetric, so no client/server
    dispatch changes).
  - external adapter: ChildrenOf loads the resource's state from the store and
    sets RefParams.State when the plugin is stateful (best-effort; a load
    failure just yields no children rather than blocking the graph).
  - plugins/k8s: advertises CapabilityChildren and implements ChildrenOf —
    HelmRelease enumerates the rendered release manifest Helm stores in-cluster
    (helm get manifest → parseObjects → apiVersion/kind/name); Manifest reports
    its persisted object refs directly (no cluster round-trip). Platform is
    intentionally omitted — its component HelmReleases already surface via Plan.

The enumerated objects are non-openctl kinds (apps/v1 Deployment, …), so the
graph renders them as observed/dimmed leaves and doesn't try to expand them
(registry.For rejects a non-openctl apiVersion). Pods aren't in the release
manifest, so this surfaces the release's declared objects, not individual pods
(a live owner-ref walk would be a future addition).

Verified: unit (manifest→refs mapping incl. core-vs-grouped apiVersion +
nameless-doc skipping; adapter state-threading proven end-to-end over the pipe)
+ e2e-vs-k3d (podinfo install → ChildrenOf returns the real apps/v1 Deployment
and v1 Service). Resolves the upward-into-pods follow-on from the deployment
model (Phase 6 / #128).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant