Skip to content

Commit c1a447b

Browse files
authored
docs(tenants): document namespace layout and parent/child derivation (#479)
## What Adds two new sections to the Tenant System guide (`/docs/v1/guides/tenants/`) that document how tenant workload namespace names are constructed and how downstream integrations should walk the tenant tree without string-parsing namespace names. ## Why Tenant workload namespaces follow two different rules depending on depth: tenants created directly under `tenant-root` get a flat name (`tenant-alpha`), while tenants created at any deeper level get a hierarchical name (`tenant-alpha-beta`, `tenant-alpha-beta-gamma`). This split is not documented anywhere in `content/en/docs/v1/`, so anyone building dashboards, audit tooling, cost-allocation jobs, or policy engines on top of Cozystack has to infer it from the chart templates. The natural first attempt — `strings.Split(namespace, "-")` to derive a parent — is wrong for one of the two cases regardless of which heuristic you pick, and integrations that try it run into silent breakage on root-level or deep-level tenants. The new material: - **Tenant Namespace Layout** — states the two rules plainly, shows a four-row lookup table from `root` down to `root/alpha/beta/gamma`, and links to the two source-of-truth files (`packages/apps/tenant/templates/_helpers.tpl` and `pkg/registry/apps/application/rest.go::computeTenantNamespace`) so a reader can audit the claim against the implementation. - **Deriving Parent and Child Relationships** — explains why string-parsing the namespace is unreliable and documents the `Tenant` CR fields that should be used instead: `metadata.namespace` always equals the parent's workload namespace, `status.namespace` equals the tenant's own workload namespace, and listing children of a tenant with workload namespace `N` is `list Tenants where metadata.namespace == N`. This pattern is stable regardless of whether the tenant is a direct child of `tenant-root` or a deeper descendant. No existing content is moved or rewritten; the sections are inserted between *Tenant Naming Limitations* and *Reference*, where readers looking for naming details will naturally land. ## Verification - `hugo` builds cleanly; the tenants guide renders with both new sections. - Table entries cross-checked against `packages/apps/tenant/templates/_helpers.tpl::tenant.name` in the current cozystack tree (the helper fails the Helm release if the tenant name itself contains a dash, which is why the "namespace fragments never contain tenant-internal dashes" note is accurate). - The "`metadata.namespace` equals the parent's workload namespace" claim matches Kubernetes semantics — `Tenant` CRs are namespace-scoped and stored in the parent's workload namespace by construction. - The aggregated API code path (`pkg/registry/apps/application/rest.go::computeTenantNamespace`) implements the same flat/hierarchical split, so `status.namespace` on a `Tenant` CR is a reliable mirror of the workload namespace. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added a tenant namespace layout describing deterministic derivation with an example table * Guidance for integrations on determining parent vs tenant workload namespaces, plus a kubectl example * Documented RFC‑1123 naming constraints, a 63‑character limit for deeper names, and the failure mode when derivation is rejected * Minor formatting and cleanup for clarity <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 parents b299b57 + d37bf26 commit c1a447b

1 file changed

Lines changed: 85 additions & 0 deletions

File tree

content/en/docs/v1.2/guides/tenants/_index.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,91 @@ For example:
9797
- A user tenant is named `foo`, which results in `tenant-foo`.
9898
- However, a tenant cannot be named `foo-bar`, because parsing names like `tenant-foo-bar` can be ambiguous.
9999

100+
### Tenant Namespace Layout
101+
102+
Each tenant corresponds to a Kubernetes workload namespace. The `root`
103+
tenant is a special case: its namespace is hardcoded to `tenant-root`.
104+
For every nested tenant, the namespace is derived from its parent's
105+
workload namespace and its own name using two rules:
106+
107+
- A tenant created directly inside `tenant-root` gets the namespace
108+
`tenant-<name>`. The parent's `tenant-root-` prefix is **not** included.
109+
- A tenant created at any deeper level gets the namespace
110+
`<parent-workload-namespace>-<name>`, appending the child's name to the
111+
parent's full namespace.
112+
113+
For example, starting from `tenant-root`:
114+
115+
| Tenant path | Workload namespace |
116+
| --- | --- |
117+
| `root` | `tenant-root` |
118+
| `root/alpha` | `tenant-alpha` |
119+
| `root/alpha/beta` | `tenant-alpha-beta` |
120+
| `root/alpha/beta/gamma` | `tenant-alpha-beta-gamma` |
121+
122+
Both the `tenant` Helm chart and the aggregated API implement these rules
123+
when a new tenant is created:
124+
125+
- the Helm chart helper in
126+
[`packages/apps/tenant/templates/_helpers.tpl`](https://github.com/cozystack/cozystack/blob/main/packages/apps/tenant/templates/_helpers.tpl)
127+
computes the namespace for the child release being installed,
128+
- the `computeTenantNamespace` function in
129+
[`pkg/registry/apps/application/rest.go`](https://github.com/cozystack/cozystack/blob/main/pkg/registry/apps/application/rest.go)
130+
publishes the same value as `status.namespace` on the `Tenant` CR.
131+
132+
Because tenant names themselves are constrained to be alphanumeric (see
133+
*Tenant Naming Limitations* above), namespace fragments never contain
134+
tenant-internal dashes.
135+
136+
{{% alert color="warning" %}}
137+
Kubernetes namespace names are RFC 1123 labels and cannot exceed **63
138+
characters**. Because deeper tenants accumulate the full ancestor chain
139+
into their workload namespace name (`tenant-alpha-beta-gamma`), long tenant
140+
names combined with deep nesting can bump into this limit. Plan the
141+
hierarchy accordingly: short tenant names at deeper levels, or shallower
142+
trees when long names are unavoidable. Kubernetes will reject the namespace
143+
creation if the computed name exceeds 63 characters, and the containing
144+
`tenant` Helm release will surface that rejection as a reconcile failure.
145+
{{% /alert %}}
146+
147+
### Deriving Parent and Child Relationships
148+
149+
Downstream integrations — custom dashboards, audit tooling, cost-allocation
150+
jobs, policy engines — sometimes need to walk the tenant tree to render
151+
breadcrumbs, compute inherited settings, or scope queries. A tempting
152+
shortcut is to derive the parent namespace by splitting the workload
153+
namespace on `-` and rebuilding it minus the last segment. That works
154+
today only because tenant names are constrained to be alphanumeric, so
155+
the `-` character unambiguously separates ancestor segments; it also
156+
assumes the current namespace-generation rules never change. Both
157+
assumptions are implementation details, not a stable contract.
158+
159+
The stable contract is the `Tenant` custom resource itself. Cozystack
160+
stores every `Tenant` CR in its parent's workload namespace, so:
161+
162+
- **`metadata.namespace`** of a `Tenant` CR equals the **parent's** workload
163+
namespace. This is the reliable pointer to the parent — no string parsing
164+
required.
165+
- **`status.namespace`** of a `Tenant` CR equals the tenant's **own** workload
166+
namespace (the one where the tenant's applications, nested tenants, and
167+
`HelmRelease`s live).
168+
- To list the direct children of a tenant with workload namespace `N`, list
169+
`Tenant` CRs whose `metadata.namespace == N`. With `kubectl`, this is a
170+
single command against the parent's workload namespace:
171+
172+
```bash
173+
kubectl get tenants --namespace <parent-workload-namespace>
174+
```
175+
176+
The `tenants` resource is served by the Cozystack aggregated API
177+
(`apps.cozystack.io/v1alpha1`), so `cozystack-api` must be running and
178+
reachable from the client. Run `kubectl api-resources --api-group apps.cozystack.io`
179+
to confirm the resource is visible from your kubeconfig context.
180+
181+
This approach is stable regardless of whether the tenant is a direct child of
182+
`tenant-root` or a deeper descendant, and it survives any future adjustments
183+
to the namespace layout because it does not depend on the layout at all.
184+
100185

101186
### Reference
102187

0 commit comments

Comments
 (0)