feat(computeplane): provision a ComputePlane tenant module for untrusted-code apps - #3280
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds ComputePlane package sources, charts, application definitions, tenant configuration and HelmRelease wiring, configurable cluster node groups, release-name validation, and ingress-nginx gating with expanded Helm tests. ChangesComputePlane platform and tenant integration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant TenantConfig
participant TenantModule
participant ComputeplanePackage
participant ComputeplaneCluster
TenantConfig->>TenantModule: set computeplane=true
TenantModule->>ComputeplanePackage: reference computeplane application artifact
ComputeplanePackage->>ComputeplaneCluster: create computeplane-cluster HelmRelease
ComputeplaneCluster->>ComputeplaneCluster: apply addons and nodeGroups values
Possibly related issues
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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 |
The illustrative HelmRelease conflated the tenant-rendered module release with the cluster release the module renders, making extra/computeplane look bypassed. Show both levels — tenant chart → extra/computeplane (release computeplane-module) → re-sourced apps/kubernetes (release computeplane → the computeplane-admin-kubeconfig contract) — matching the implementation in cozystack/cozystack#3280, and pin the tenant toggle as the computeplane bool. Assisted-By: Claude Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
| namespace: cozy-system | ||
| path: / | ||
| variants: | ||
| - name: kubevirt |
There was a problem hiding this comment.
Why is this kubevirt?
There was a problem hiding this comment.
Documented in the file now: the variant mirrors the kubernetes-application variant whose chart it re-sources — the wrapped substrate is the kubevirt flavour of apps/kubernetes, and the artifact naming contract (cozystack-computeplane-application-kubevirt-*) carries that provenance. A future non-kubevirt substrate would arrive as a sibling variant.
Timofei Larkin (lllamnyp)
left a comment
There was a problem hiding this comment.
Blocking on process first: this cannot merge until cozystack/community#33 is approved and merged. The proposal is approved but still open, and this PR implements its surface directly — the module contract (release name, kubeconfig secret, tenant-visible knobs) is exactly what the proposal fixes. Merging the implementation while the design can still move means any late revision lands as a breaking change to an already-shipped module kind. Please land #33 first, then rebase this.
The module itself is in good shape. Wrapping apps/kubernetes with operator-fixed inline spec.values is the right lever — Flux merges spec.values after valuesFrom, so the addon pins genuinely cannot be disabled through cozystack-values. Reserving the canonical release name for the cluster HelmRelease so Kamaji writes computeplane-admin-kubeconfig, and excluding that Secret from tenant-visible secrets, is a clean way to hold the credential. The source-only re-declaration of apps/kubernetes is safe as built: the package reconciler skips components without an install: block, so the duplicate component yields an artifact and never a colliding HelmRelease. And the cozystack-engine edge is correct — it really does gate the computeplane-rd release that would otherwise fail to render against a missing ApplicationDefinition CRD.
Findings below, ranked.
1. packages/apps/kubernetes/templates/ingress.yaml — the Proxied Service now renders on every cluster, including those with no ingress-nginx
The guard went from and (eq exposeMethod "Proxied") .hosts to if eq exposeMethod "Proxied", with a new end closing the hosts condition above the Service. That's the intended change for ComputePlane, but it drops the only thing that was standing in for "the operator actually wants ingress here": exposeMethod defaults to Proxied while addons.ingressNginx.enabled defaults to false.
Rendering the chart against its own tests/values-ci.yaml (which configures no ingressNginx at all) emits nothing before this change and emits <release>-ingress-nginx after it. So on upgrade, every existing kind: Kubernetes application gains a Service in its tenant namespace whose selector (node-role.kubernetes.io/ingress-nginx: "") matches no pods. It is not invisible either — packages/system/kubernetes-rd/cozyrds/kubernetes.yaml lists kubernetes-{{ .name }}-ingress-nginx under services.include, so it surfaces in the tenant dashboard advertising ports 80/443 that route nowhere.
Suggested fix — gate on the addon rather than on hosts, which still satisfies ComputePlane since it sets enabled: true explicitly:
{{- if and .Values.addons.ingressNginx.enabled (eq .Values.addons.ingressNginx.exposeMethod "Proxied") }}
2. packages/core/platform/templates/bundles/iaas.yaml — the toggle ships in system, the backing package ships in iaas
Every other tenant module toggle (etcd, monitoring, ingress, gateway, seaweedfs) is backed by a package in the system bundle, so the toggle always resolves. ComputePlane is the first whose package lives in iaas, while the computeplane field itself arrives via tenant-rd in the system bundle. Bundles are independent booleans with no cross-validation.
Failure: on a system + paas install, a tenant sets computeplane: true and gets a HelmRelease pointing at a cozystack-computeplane-application-kubevirt-computeplane artifact that does not exist. With install.remediation.retries: -1 it retries forever, with no diagnostic and no dashboard entry (the ComputePlane kind is not registered either, since computeplane-rd is in the same absent package). Either move the package to the system bundle, or have the tenant chart fail loudly when the module is toggled on without its package.
3. packages/extra/computeplane/values.schema.json — nodeGroups is unvalidated, and can defeat the pinned ingress addon
The module declares nodeGroups as a bare type: object with x-kubernetes-preserve-unknown-fields: true. The wrapped kubernetes app validates the same knob against a typed schema (diskSize, gpus, instanceType, kubelet, maxReplicas, minReplicas, resources, roles, storageClass) with per-field enums and patterns. Two consequences:
- A bad
instanceTypeor a misspelled field draws no admission error. It lands in the cluster HelmRelease and fails deep in the wrapped chart's render, surfacing as a stuck release rather than a rejected write. - More importantly: user-supplied nodeGroups are authoritative and are not merged with the default
md0(per thekubernetes.nodeGroupshelper), and the ingress-nginx controller carriesnodeSelector: node-role.kubernetes.io/ingress-nginx: "". A tenant who supplies any nodeGroups withoutroles: [ingress-nginx]leaves the controller Pending indefinitely and the ComputePlane's ingress never comes up.
The README states "the security posture is fixed by the module and cannot be overridden", but ingress availability is in practice reachable through the one knob the module does expose. Reusing the kubernetes app's nodeGroups schema addresses the validation half; the roles requirement wants either defaulting in the module or an explicit warning in the parameter docs.
4. packages/core/platform/sources/computeplane-application.yaml — the comment overstates what the kubernetes-application edge does
The comment explains the edge as needed "because the wrapped apps/kubernetes chart renders child HelmReleases whose chartRefs point at that package's addon artifacts". Variant dependsOn only gates HelmRelease creation for components carrying an install: block — here that is computeplane-rd alone. The cluster HelmRelease is rendered by the tenant chart and is not ordered by this edge, and the addon artifacts it references are produced unconditionally by the PackageSource reconciler regardless of dependency readiness. The edge is harmless and the artifact naming is right; the stated reason just isn't the operative one.
Checked and fine
- Re-running
hack/update-crd.shinpackages/extra/computeplanereproduces the committed ApplicationDefinition byte-for-byte, so the generated RD is in sync with the generator. - The
releaseName: computeplane-module/ object-namecomputeplanesplit does not break application lookup, which keys off the HelmRelease object name rather thanspec.releaseName. - The victoria-metrics-operator gate that other tenant modules carry is correctly absent —
apps/kubernetesrenders noVM*resources on the management side. - All touched helm-unittest suites pass.
Not blocking
No e2e coverage is added. Standing up a full Kamaji + KubeVirt cluster in CI is a real cost and the unit suites do pin the module contract meaningfully, so this reads as a reasonable follow-up rather than a merge condition — but it is worth an issue so it does not get lost behind the placement work.
177927e to
5cde79f
Compare
|
All four findings addressed, and rebased on main now that cozystack/community#33 is merged:
The e2e follow-up is filed separately so it does not get lost behind the |
Summary of ChangesHello, 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 implements the ComputePlane module, which provides a secure, isolated Kubernetes cluster environment for running untrusted code. By wrapping the standard Kubernetes application with operator-fixed values, it ensures that security-critical addons are always enabled while withholding admin credentials from the tenant. The changes include new module definitions, updated tenant configuration schemas, and improvements to the ingress proxying logic to support external management access. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. 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 the 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 counterproductive. 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. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces the 'ComputePlane' feature, a single-tenant, Cozystack-managed cluster for running untrusted-code applications. It adds the computeplane configuration option to the tenant API, creates the computeplane extra package and its resource definitions, wires it into the IaaS bundle, and updates the ingress configuration in the kubernetes application to support proxied routing. The review feedback suggests improving Helm template safety in ingress.yaml by using a nil-safe field walking pattern to prevent potential nil pointer dereference panics, and pinning the Flux operator manifests artifact in cluster.yaml to a specific stable version instead of using the :latest tag to ensure reproducible deployments.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| {{- if and .Values.addons.ingressNginx.enabled (eq .Values.addons.ingressNginx.exposeMethod "Proxied") }} | ||
| {{- if .Values.addons.ingressNginx.hosts }} |
There was a problem hiding this comment.
To prevent potential nil pointer dereference panics when addons or ingressNginx is omitted or null in custom values, use the repository's established parenthesized nil-safe field walking pattern (e.g., ((( .Values.addons ).ingressNginx).enabled)). This ensures that nested field accesses safely evaluate to nil (which is falsy) rather than causing a template rendering error.
{{- if and (((.Values.addons).ingressNginx).enabled) (eq (((.Values.addons).ingressNginx).exposeMethod) "Proxied") }}
{{- if (((.Values.addons).ingressNginx).hosts) }}| flux-instance: | ||
| instance: | ||
| distribution: | ||
| artifact: "oci://ghcr.io/controlplaneio-fluxcd/flux-operator-manifests:latest" |
There was a problem hiding this comment.
Using the :latest tag for the Flux operator manifests can lead to non-reproducible deployments and unexpected breaking changes when upstream updates their manifests. It is highly recommended to pin this to a specific stable version (e.g., v0.14.0 or another tested release) to ensure stability and predictability in production environments.
artifact: "oci://ghcr.io/controlplaneio-fluxcd/flux-operator-manifests:v0.14.0"
Timofei Larkin (lllamnyp)
left a comment
There was a problem hiding this comment.
All four findings from the previous round are addressed on their merits, with tests, and community#33 has merged — the process block is cleared. The addon gate on the Proxied Service is exactly right, the corrected dependsOn comment now describes what variant dependencies actually do, and the nodeGroups typedefs are a real improvement. Requesting changes on one blocking issue plus a few smaller items.
Blocking: the release-name divergence is unrepresentable in the aggregated API
packages/apps/tenant/templates/computeplane.yaml sets spec.releaseName: computeplane-module, and packages/extra/computeplane/templates/cluster.yaml pins spec.releaseName: computeplane on the inner cluster HelmRelease. That split cannot survive a write through the aggregated API.
computeplane-rd declares release.prefix: "", so a ComputePlane object named computeplane maps onto the same HelmRelease the tenant chart renders. ApplicationDefinitionRelease has exactly three fields — ChartRef, Labels, Prefix — with no way to express a release name, and convertApplicationToHelmRelease never assigns Spec.ReleaseName. REST.Update replaces the live spec with that rebuilt one, carrying over only labels and the shard key, and Flux does not pin releaseName, so the field is silently dropped.
The first edit through kubectl or the dashboard therefore blanks releaseName, it defaults to the object name computeplane, and that is the exact Helm release computeplane-cluster owns — two HelmReleases driving one Helm storage record with two different charts. The tenant chart then reconciles at its 5m interval and writes computeplane-module back, so the name oscillates. Blast radius is the Kamaji control plane, the worker MachineDeployments and the admin kubeconfig Secret.
This is on the main path rather than a corner: nodeGroups is the module's only tenant-facing knob, and the aggregated API is the only way to set it, since the tenant chart's module HelmRelease passes valuesFrom and no values: at all.
Why this module and not the others
Worth spelling out, because the fix depends on it. Every other tenant module that renders a nested HelmRelease derives the inner name from the release and lets the inner release name default to the object name: external-dns and monitoring use {{ .Release.Name }}-system, seaweedfs uses {{ .Release.Name }}-db. None of them sets spec.releaseName, and computeplane.yaml is the only template under packages/apps/tenant/templates/ that sets it at all.
computeplane inverts the pattern: it hardcodes the inner object name and claims the canonical release name for the inner release, which pushes the outer module release off onto a suffixed one. It does that for a real reason — Kamaji derives the computeplane-admin-kubeconfig Secret from the Helm release name, so the inner release name is load-bearing for an external contract. No other module has a downstream consumer that reads its inner release name, which is why suffixing the inner one is free for all of them and not for this one.
So the general shape the aggregated API can express is exactly one release name per object, prefix + name. Any module whose outer release name diverges from that is unrepresentable — and on closer look the divergence buys nothing, because the inner release name is only load-bearing in the sense that something downstream has to know it, not that it has to be any particular string.
Fix: leave both releaseName fields blank, as every other module does
Drop spec.releaseName from both HelmReleases and let Flux derive them. Flux defaults releaseName to [TargetNamespace-]Name, and neither HelmRelease sets targetNamespace, so the outer module release becomes computeplane from its object name and the inner cluster release becomes computeplane-cluster from its own. Nothing collides, nothing diverges, and the aggregated API round-trips cleanly because prefix + name is then exactly what both already are — an Update that rebuilds the spec without releaseName reproduces the same release name rather than changing it.
This also puts the module back on the same pattern as external-dns, monitoring and seaweedfs, which is worth something on its own.
The consequence is that Kamaji writes the admin kubeconfig to computeplane-cluster-admin-kubeconfig instead of computeplane-admin-kubeconfig, which is fine — nothing consumes it yet, since placement: ComputePlane is a deferred follow-up — but it must be changed everywhere in the same commit.
The security-critical one is packages/system/computeplane-rd/cozyrds/computeplane.yaml, whose secrets.exclude withholds the credential by literal name under resourceNames. Rename the release without updating that entry and the exclusion silently stops matching, so the cluster admin kubeconfig becomes visible to the tenant — precisely the credential the whole design exists to withhold. The remaining references are prose and test comments: packages/extra/computeplane/templates/cluster.yaml, packages/extra/computeplane/README.md, packages/apps/tenant/templates/computeplane.yaml, packages/extra/computeplane/tests/cluster_test.yaml and packages/apps/tenant/tests/computeplane_module_test.yaml.
Since the merged proposal names computeplane-admin-kubeconfig when describing the deferred placement injection, that line wants a follow-up amendment so the future kubeConfig.secretRef is written against the derived name.
This wants two tests: a helm-unittest case asserting the tenant module HelmRelease carries no spec.releaseName, and one asserting the excluded secret name in computeplane-rd matches the name the inner release actually produces — the second is the one that would catch a future rename re-exposing the credential.
Add the release-name guard, and file the gap for the rest
packages/extra/computeplane has no check-release-name.yaml. Four of the nine extra charts ship one — bootbox, etcd, ingress, monitoring — so it is an established pattern rather than a new demand, and it is cheap here. It also fails closed on the issue above: a dropped releaseName yields computeplane, which the guard rejects loudly instead of silently colliding two releases.
The other four charts without a guard (external-dns, gateway, info, seaweedfs) have the same latent exposure and are out of scope for this PR — worth a tracking issue rather than scope creep here. Note that community#39 proposes cardinality as a declarative capability on ApplicationDefinition, which would replace all of these hand-rolled per-chart guards; the issue should reference it so the guards are understood as a stopgap.
Close the nodeGroup object in the schema
The typedefs landed, but values.schema.json still has no additionalProperties: false, so unlisted nodeGroup keys pass through to the wrapped chart unvalidated — maxUnhealthy and nodeStartupTimeout among them. For a module whose security argument is that the chart is the profile, the README's "only the cluster shape the module exposes" is not yet true.
Close it through the generator by adjusting the NodeGroup typedef in values.yaml and re-running make -C packages/extra/computeplane generate, rather than hand-editing the schema or the embedded openAPISchema. If the generator cannot emit additionalProperties: false, drop the claim from the README and values.yaml and state plainly that unlisted fields pass through. A helm-unittest case setting an unlisted nodeGroup field and asserting rejection pins whichever way it goes.
Two notes, not change requests
The etcd dependency is a precedent worth recording, not a defect. A ComputePlane with no tenant etcd gets no cluster-autoscaler, so the default nodeGroups: {} scale-from-zero md0 never receives its trigger and the cluster provisions no workers. That is not specific to the pinned addons — none of the tenant Kubernetes machinery works without etcd, and that is the intended design. What is new is that this makes one tenant module depend on another, which the module system has no way to express or surface today. A tenant enabling computeplane without etcd gets silence rather than a diagnostic. Worth deciding deliberately whether to document the prerequisite in the module README and the tenant README, or to surface it, and worth capturing the module-depends-on-module precedent somewhere durable — plausibly as input to community#39, which is already reshaping how modules declare themselves.
The package guard's blast radius is a machinery-wide deficiency. Helm's fail aborts the whole chart, so a tenant flipping computeplane: true on an install without the iaas bundle freezes reconciliation of everything else the tenant chart owns — namespace labels, quotas, network policy, ingress, monitoring — behind remediation.retries: -1. The guard itself is correct and the mechanism checks out; the problem is that a tenant-settable boolean can wedge the tenant's entire configuration surface, and that is true of any fail in the tenant chart rather than anything this PR introduced. community#39's move away from the special-cased extra bucket is the right place to fix the class. No change requested here beyond awareness.
Smaller
packages/apps/kubernetes/tests/ingress_proxied_gate_test.yaml covers enabled/disabled and hosts/no-hosts, but not enabled: false with exposeMethod: Proxied and a non-empty hosts list. That combination used to render an Ingress and now renders nothing. It is almost certainly the intended reading of "the addon is off", but it is a real behaviour change for any existing cluster in that state — worth a case pinning it deliberately so it reads as a decision.
packages/system/computeplane-rd/values.yaml contains a literal {}; the sibling seaweedfs-rd ships no values.yaml at all. Drop it for consistency.
|
The blocking issue is fixed as prescribed, plus the smaller items:
|
Timofei Larkin (lllamnyp)
left a comment
There was a problem hiding this comment.
The release-name fix is right, and deriving both names from object names is the change that makes this representable through the aggregated API. check-release-name, the pinned flux-operator version instead of :latest, and the two pushbacks — the -rd values.yaml convention and the generator's inability to emit additionalProperties: false — are all correct; thanks for checking those rather than just applying them.
Most of what remains is drift between what the code does and what its comments, docs and PR body say. One item is functional, and one is a correction to something the previous round asked for.
Retracting the secrets.exclude framing — the previous round had the mechanism wrong
The last review called the computeplane-admin-kubeconfig entry security-critical and asked for a test pinning it to the derived release name. That was wrong, and the resulting hack/check-computeplane-kubeconfig-exclusion.bats should come back out.
matchResourceToExcludeInclude in internal/lineagecontrollerwebhook/matcher.go returns the value of matchResourceToSelectorArray(..., resources.Include), and that function returns false on an empty slice. With include: [], no secret in a ComputePlane's lineage is ever labelled internal.cozystack.io/tenantresource: "true", and pkg/registry/core/tenantsecret/rest.go surfaces only labelled secrets. It is an allowlist, not a denylist — the exclude branch never becomes load-bearing, so renaming the release could not have exposed the credential.
The clearest evidence is in this PR already: packages/apps/kubernetes/templates/helmreleases/fluxcd.yaml consumes two Kamaji credentials, {{ .Release.Name }}-admin-kubeconfig and {{ .Release.Name }}-kubeconfig. The exclude list names only the first. If that list were the guard the comments claim, the second cluster-admin credential would already be exposed — it is not, because the empty include list withholds everything.
So the withholding is rename-proof by construction, which is a stronger property than the one the previous round asked to pin. The suggested resolution is to drop the resourceNames entry and keep exclude: [] / include: [] as seaweedfs-rd does, rewrite the three comment blocks that assert the rename coupling — in computeplane-rd/cozyrds/computeplane.yaml, extra/computeplane/templates/cluster.yaml and extra/computeplane/README.md — to state the real mechanism, and delete the bats file. Keeping the literal entry as defence-in-depth is also defensible, but then it should say so and should list both secrets, so it is not mistaken for the mechanism again.
No replacement static test. The property is "a tenant-scoped client cannot read the ComputePlane's kubeconfig Secrets", which lives between the lineage webhook, the aggregated API and RBAC at runtime; a file-comparison test cannot observe it and gives false assurance about the most important security claim in the PR. That belongs in the ComputePlane e2e already filed.
The Proxied Service escapes the etcd gate
packages/apps/kubernetes/templates/ingress.yaml gates the Service on and .Values.addons.ingressNginx.enabled (eq ... "Proxied"), but the ingress-nginx addon HelmRelease that backs it is gated on and .Values.addons.ingressNginx.enabled .Values._namespace.etcd. ComputePlane pins the addon on, so on an etcd-less tenant the chart emits computeplane-cluster-ingress-nginx with no controller behind it — confirmed by rendering packages/apps/kubernetes against tests/values-ci-no-etcd.yaml with the addon enabled and exposeMethod: Proxied.
That is the same selector-less-Service outcome the addon gate was added to prevent, reached by a different route, and it contradicts the comment now sitting above the Service. It also breaks the etcd-less contract the rest of the chart maintains, where the render collapses to the awaiting-etcd beacon and nothing else.
Adding .Values._namespace.etcd to the Service condition fixes it. A case in ingress_proxied_gate_test.yaml with the addon enabled and _namespace.etcd empty, asserting nothing renders, pins it — that one is a genuine render-behaviour assertion rather than a drift guard, so it is worth having.
The etcd prerequisite: two details wrong in the README
Both are worth correcting whether or not the guard becomes a hard fail.
"the tenant must have the etcd module enabled" is stricter than reality — packages/apps/tenant/templates/namespace.yaml inherits _namespace.etcd from the parent tenant, so an ancestor's etcd satisfies the wrapped chart, and a tenant following this literally provisions a redundant one. "gives no diagnostic" is also not quite right: packages/apps/kubernetes/templates/cluster.yaml emits the awaiting-etcd ConfigMap as a status beacon. It is a weak diagnostic — both HelmReleases still report Ready over an empty shell — but it exists and should be named.
Whether to escalate this from documentation to a render-time fail alongside the package guard is your call, and there is a reasonable argument either way now that the beacon is in the picture. If it stays documentation, the two corrections above are enough.
placement: ComputePlane is advertised but not shipped
The Tenant field description in api/apps/v1alpha1/tenant/types.go says "Applications with placement: ComputePlane are routed onto it." That field does not exist anywhere in Go, charts or CRDs — it is the deferred follow-up. The text is baked into the Tenant openAPISchema in tenant-rd, so it renders in the dashboard, and it is repeated in packages/apps/tenant/values.yaml, values.schema.json and README.md.
An operator enabling computeplane: true today therefore provisions a Kamaji control plane and KubeVirt workers that nothing can be scheduled onto, with the field description implying otherwise. Reword to state what ships now and note the routing is not yet available, then regenerate through make generate so all four copies follow rather than hand-editing them.
The larger question is whether the toggle should ship before its consumer exists at all. Gating it until the placement work lands would avoid advertising an inert capability, and is worth a deliberate decision rather than defaulting to shipping.
Drift
packages/extra/computeplane/templates/cluster.yaml still names the Service computeplane-ingress-nginx; after the release rename it renders as computeplane-cluster-ingress-nginx. The per-app management Ingress follow-up will be written against whatever this comment says, so it is worth fixing now.
The PR body still describes the pre-rename scheme — module release taking the -module suffix, canonical computeplane reserved for the cluster, Secret computeplane-admin-kubeconfig. The shipped code is the inverse. Since the release note is drafted from this text, it should be updated before merge.
None of the items in this section need tests; the codegen check already covers the generated copies.
Recommended, not blocking
The ingress-role guard requires some group to carry roles: [ingress-nginx] but not that the group can ever run a pod — a matching group with maxReplicas: 0 reproduces the Pending-forever failure the guard exists to prevent. Requiring maxReplicas >= 1 on the matching group would close it.
internal.cozystack.io/computeplane: "true" on the cluster HelmRelease has no reader anywhere in the tree. If it is a hook for the placement follow-up, a comment saying so would stop the next person removing it as dead.
The package guard fails closed on a nil lookup, where the comparable guard in the platform chart fails only when the lookup returns something. The Capabilities gate covers offline helm template, but a client-side render against a live cluster's discovery would trip it and fail the whole tenant render. Worth confirming no such path exists in the packaging pipeline.
Finally, worth confirming against the design that module self-provisioning is acceptable: with release.prefix: "", a tenant can create a ComputePlane object named computeplane through the aggregated API in a tenant where the operator left the toggle off, and check-release-name blocks every name except that one. This is true of every dashboard.module: true RD and is not introduced here — but ComputePlane is the first module where the unauthorised object is a VM-backed Kubernetes cluster, which changes the cost of the gap.
|
Round three addressed (d1dddd7):
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/apps/tenant/README.md`:
- Line 85: The computeplane README entry contradicts itself about tenant
kubeconfig access. Update the row to state that
computeplane-cluster-admin-kubeconfig is platform-internal and unavailable to
tenant administrators, or remove the external-catalog workaround if the include
allowlist prevents its use; keep the no-admin-kubeconfig tenant behavior
explicit.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 494d8cd0-252f-4938-bb3c-f43e1c638f06
📒 Files selected for processing (11)
api/apps/v1alpha1/tenant/types.gopackages/apps/kubernetes/templates/ingress.yamlpackages/apps/kubernetes/tests/ingress_proxied_gate_test.yamlpackages/apps/tenant/README.mdpackages/apps/tenant/templates/computeplane.yamlpackages/apps/tenant/values.schema.jsonpackages/apps/tenant/values.yamlpackages/extra/computeplane/README.mdpackages/extra/computeplane/templates/cluster.yamlpackages/system/computeplane-rd/cozyrds/computeplane.yamlpackages/system/tenant-rd/cozyrds/tenant.yaml
🚧 Files skipped from review as they are similar to previous changes (8)
- packages/system/tenant-rd/cozyrds/tenant.yaml
- packages/apps/tenant/values.yaml
- packages/apps/kubernetes/templates/ingress.yaml
- api/apps/v1alpha1/tenant/types.go
- packages/apps/tenant/values.schema.json
- packages/apps/kubernetes/tests/ingress_proxied_gate_test.yaml
- packages/extra/computeplane/templates/cluster.yaml
- packages/apps/tenant/templates/computeplane.yaml
Timofei Larkin (lllamnyp)
left a comment
There was a problem hiding this comment.
LGTM. Round three is closed and I verified the fixes rather than reading the summary — rendering apps/kubernetes against the no-etcd values with the addon enabled and exposeMethod: Proxied now produces nothing, which was the one functional bug left; the secrets framing is corrected to the allowlist mechanism with the bats cross-check removed; and re-running the generators for both computeplane and tenant produces zero drift, so the reworded toggle description is genuinely generated across all four copies. Suites green at 9 / 26 / 90 / 186.
Taking exclude: [] / include: [] over defence-in-depth was the right call — a literal name list would have invited the same misreading a second time, and the empty include list withholds by construction regardless of naming.
Two notes, neither blocking and neither needing a change here.
The etcd prerequisite staying documentation rather than a render-time fail is a reasonable call, and the rewritten README is accurate now that ancestor etcd counts and the awaiting-etcd beacon is named. The stated reason does not hold, though: packages/apps/tenant/templates/namespace.yaml computes the effective etcd as $parentNamespace.etcd overridden by .Values.etcd, so enabling etcd and computeplane in a single edit would satisfy a guard in that same render — the enable-both-in-one-edit flow would not have broken. The decision stands on the beacon plus the docs being proportionate; it just should not be cited later as a precedent for skipping a guard that would have worked.
On the maxReplicas >= 1 guard: I misread the template on first pass and thought the fail sat inside the range, which would have made it order-dependent across node groups. It does not — the range closes before the check. Flagging only so the observation is not mistaken for an unreported concern.
Shipping the toggle ahead of in-tree placement routing is fine given cozyllm targets the kubeconfig Secret directly; the capability is not inert, which was the actual worry. Module self-provisioning belonging with community#39's protection capability rather than a special case here is the right home for it.
|
let's wait for v1.5 release, we'll take it into v1.6 |
|
Rebased on main over the kubernetes/kubernetes-nodes split (#3314). The split is additive — the base app keeps |
e0d8c0a to
1111f4d
Compare
…the kubernetes app Add the operator-owned computeplane tenant module per the revised design (cozystack/community design-proposals/compute-plane): an extra/computeplane chart that provisions a single-tenant, Cozystack-managed cluster for untrusted-code apps by deploying the ordinary apps/kubernetes chart with operator-fixed values (fluxcd, cert-manager, ingress-nginx Proxied). The package re-declares apps/kubernetes as its own source-only component, so the module is self-contained; the cluster release is named computeplane so Kamaji writes the admin kubeconfig to computeplane-admin-kubeconfig — the contract consumed by placement: ComputePlane applications. The tenant holds no admin kubeconfig and can set cluster shape (nodeGroups) only. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
Wire the computeplane module into the tenant chart as a bool toggle, matching the other tenant modules. The module's own Helm release takes the -module suffix so the canonical release name computeplane (and with it the computeplane-admin-kubeconfig Secret) stays reserved for the cluster HelmRelease the module renders. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
…hosts The proxy Service was only rendered when addons.ingressNginx.hosts was non-empty, but external consumers — e.g. a ComputePlane app's per-app management Ingress — need to route to the tenant cluster's ingress-nginx without the module knowing every app host in advance. Render the Service whenever exposeMethod is Proxied; the Ingress still requires hosts. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
…ckage The computeplane-rd component installs an ApplicationDefinition, whose CRD is served by cozystack-engine — hack/check-applicationdefinition-crd-ordering.bats enforces the edge for every package with a system/*-rd component. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
…on is enabled exposeMethod defaults to Proxied while the addon defaults to off, so gating the Service on exposeMethod alone emitted a selector-less Service — surfaced in the tenant dashboard — on every cluster that runs no ingress-nginx at all. Gate on the addon instead; ComputePlane sets enabled: true explicitly, and the Service still renders without a hosts list. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
…kage The computeplane-application package ships in the iaas bundle while the toggle arrives with tenant-rd in the system bundle, and bundles are independent booleans. Refuse the render with a clear diagnostic instead of producing a HelmRelease that retries a nonexistent artifact forever. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
nodeGroups was a bare object: typos surfaced as a stuck release deep in the wrapped chart instead of an admission error, and groups without the ingress-nginx role silently left the pinned ingress-nginx controller (and with it the ComputePlane ingress) Pending forever, since supplied groups are not merged with the wrapped chart's default md0. Reuse the kubernetes app's nodeGroups typedefs for the schema and refuse rendering when no group carries the role. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
The dependsOn comment claimed the kubernetes-application edge orders the addon artifacts the wrapped chart references; variant dependsOn only gates install: components — the operative effect is ordering the computeplane-rd install so the ComputePlane kind is not registered before the kubernetes machinery exists. Also document why the variant is named kubevirt. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
The aggregated API cannot represent spec.releaseName: the conversion rebuilds HelmRelease specs without it, so the first tenant edit through the API dropped the module's explicit computeplane-module release name, let it default to the object name computeplane, and collided with the Helm storage record the cluster release owned — with the tenant chart writing the old name back every interval. Drop releaseName from both HelmReleases and derive names from object names, like every other tenant module. Kamaji now writes the admin kubeconfig to computeplane-cluster-admin-kubeconfig; the computeplane-rd secrets.exclude entry follows in the same change, and a bats test pins the exclusion to the name the cluster release actually produces so a future rename cannot silently re-expose the credential. Also add the check-release-name guard the other extra modules carry, and pin the fluxcd addon's distribution artifact to the flux-operator version vendored in system/fluxcd-operator instead of :latest. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
… pass-through The tenant Kubernetes machinery — including the cluster-autoscaler that brings up the default scale-from-zero md0 group — is backed by the tenant's etcd module, so a ComputePlane without it provisions no workers and gives no diagnostic; state the prerequisite in the README. Also state plainly that unlisted NodeGroup fields pass through to the wrapped chart unvalidated (the schema generator cannot close the object), with a test pinning that behaviour. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
…ith hosts set hosts with the addon disabled used to render the Ingress; the addon gate makes 'the addon is off' mean nothing renders. Pin the combination so the behaviour change reads as a decision. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
Gate the Proxied ingress Service on _namespace.etcd like the addon HelmRelease it fronts, so an etcd-less tenant with the addon pinned on does not get a selector-less Service (+ test). Correct the secrets framing: the tenant-visible surface is an allowlist (include: [] withholds everything), so drop the never-load-bearing exclude entry, the bats cross-check and the rename-coupling comments, and state the real mechanism. Fix the etcd prerequisite docs (an ancestor's etcd satisfies the wrapped chart; the awaiting-etcd beacon is the diagnostic), reword the tenant toggle to stop advertising the not-yet-shipped placement routing, require maxReplicas >= 1 on the ingress-role node group, and fix comment drift (Service name after the release rename, placement-hook label, package-guard lookup semantics). Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
The Proxied ingress Service is gated on _namespace.etcd as well as the addon toggle and exposeMethod, but the comment above it still described only the addon+exposeMethod gate — comment drift from the etcd-gate change. State both gates so the comment matches the condition it sits on. Assisted-By: Claude Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
The fluxcd addon pin was a holdover from the #3150-era model where app charts rendered nested HelmReleases into the target cluster and needed an in-cluster helm-controller. Consumers now keep their releases on the management cluster and remote-apply via spec.kubeConfig, so the management Flux resolves charts and applies manifests — nothing inside the ComputePlane reconciles HelmReleases. This also decouples the module from the FluxCD addon removal (#3379). certManager and ingressNginx stay pinned: Certificate and Ingress objects land inside the cluster and need their controllers there. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
1111f4d to
93338cc
Compare
What this PR does
Implements the ComputePlane per the merged design proposal (cozystack/community design-proposals/compute-plane; supersedes the earlier #3150 which was built on the pre-revision surface and came from a fork): a managed, isolated environment for running code-executing (untrusted-code) catalog applications.
ComputePlane is delivered as an operator-owned tenant module:
packages/extra/computeplane— the module chart. Provisions a single-tenant, Cozystack-managed cluster by deploying the ordinaryapps/kuberneteschart (Kamaji control plane + KubeVirt-VM workers) with operator-fixed values: the Cozystack-enablement addons (fluxcd+ pinned distribution artifact,certManager,ingressNginxwithexposeMethod: Proxied) are inlinespec.values, so nothing injected viavaluesFromcan disable them. The tenant can set cluster shape (nodeGroups, schema-validated) and receives no admin kubeconfig.spec.releaseName— the aggregated API rebuilds HelmRelease specs without that field, so both releases derive names from their object names. The cluster HelmRelease object iscomputeplane-cluster, and Kamaji writes the admin kubeconfig to the derived Secretcomputeplane-cluster-admin-kubeconfig— the contract futureplacement: ComputePlanerouting consumes. Tenant-visible secrets are an allowlist (include: []withholds everything), so the credential is withheld by construction.packages/system/computeplane-rd— registers theComputePlanemodule-kind (ApplicationDefinition,dashboard.module: true).packages/core/platform/sources/computeplane-application.yaml— self-contained package source: re-declaresapps/kubernetesas its own source-only component;dependsOncozystack-engine (ApplicationDefinition CRD) and kubernetes-application (registration ordering). Ships in theiaasbundle (variantkubevirt).computeplane: falsebool. The tenant chart fails loudly when the module is toggled on without the iaas package. Note: automaticplacement: ComputePlanerouting is a deferred follow-up and is not part of this PR; until it lands, external catalogs target the cluster via its kubeconfig Secret.apps/kubernetesfix — the Proxied ingressServicerenders only when the ingress-nginx addon is enabled and the tenant has etcd (mirroring the addon HelmRelease gates), independent of thehostslist.Follow-ups tracked separately:
placementrouting in cozystack-api, scoped per-service egress, visibility/mutation access control, e2e (#3369), release-name guards for the remaining extra charts (#3371).Tests: helm-unittest suites pin the module contract (object names, no explicit releaseName, tamper-proof inline addons, nodeGroups validation + ingress-role guard, unlisted-field pass-through), the tenant toggle, the package-source/bundle wiring, and the ingress Service gates.
Screenshots
Not a UI change.
Release note
Summary by CodeRabbit
computeplane, default off) to provision an isolated single-tenant environment for untrusted-code applications, including cluster creation and configurable node groups.