feat(server): cross-layer children graph — Phase 6 of the deployment model#128
Merged
Conversation
…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).
This was referenced Jul 12, 2026
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 inGetChildrenGraph, which had two limits:$refedges only from child specs, never the root's own spec — so a HelmRelease→Cluster edge never appeared.The change
GetChildrenGraphnow:$refedges from the root resource's own spec (a HelmRelease'skubeconfigPath: {$ref: Cluster/…}→ HelmRelease→Cluster edge), and$refgraph 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
expandNode(ctx, queue, work)over a BFS queue;graphBuildergains anexpandedcycle guard.fakePlannernow only composes for kindCluster(like the real k3s provider), so recursion treats VMs/Nodes as leaves.TestGetChildrenGraphSpansCrossLayer: seeds a HelmRelease manifest whosekubeconfigPath$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)
$refits ProxmoxNode yet).ChildrenOfonplugins/k8s).With this, deployment-model Phases 1–6 are all shipped.