Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
bb3ca55
add divider labels
msafwankarim Aug 12, 2026
7199e3b
docs: rewrite the Overview narrative around six capability areas
rasheedamir Aug 12, 2026
323bc01
nav labels
msafwankarim Aug 13, 2026
d6aa727
Merge branch 'main' into docs/structure-migration
msafwankarim Aug 13, 2026
59642af
docs: tell the Extensions and FinOps stories, and fix hibernation acc…
rasheedamir Aug 13, 2026
3b7ae27
Grafana integration page
aayeshaarshad Aug 13, 2026
e5f7011
Fix spelling flagged by vale
aayeshaarshad Aug 13, 2026
eb89a23
Fix punctuation flagged by LanguageTool
aayeshaarshad Aug 13, 2026
b44f9dc
Add the LGTM stack integration page
aayeshaarshad Aug 13, 2026
4bf3796
Merge branch 'main' into docs/grafana-extension-guide
rasheedamir Aug 13, 2026
ccb77f1
Reword to satisfy the Vale spelling check
aayeshaarshad Aug 13, 2026
2b1db81
Document adding the default admin user to privileged users
Felix-Stakater Aug 13, 2026
54d6f2f
Merge pull request #415 from stakater/docs/grafana-extension-guide
rasheedamir Aug 13, 2026
c81be57
restructuring
msafwankarim Aug 13, 2026
05f30b6
Merge branch 'main' into docs/structure-migration
msafwankarim Aug 13, 2026
91faa48
some updates
msafwankarim Aug 13, 2026
e5b49ad
Add an overview page to the Integrations section
aayeshaarshad Aug 13, 2026
337fcaa
Merge pull request #414 from stakater/docs/structure-migration
rasheedamir Aug 13, 2026
8155b7c
Merge pull request #417 from stakater/docs/extensions-overview
rasheedamir Aug 13, 2026
c6d6a72
Merge main into the LGTM stack page
aayeshaarshad Aug 13, 2026
d2e7a3e
Merge the Integrations overview into the LGTM stack branch
aayeshaarshad Aug 13, 2026
3faf0f3
Merge pull request #416 from stakater/docs/lgtm-stack
rasheedamir Aug 13, 2026
73aa353
docs: restructure into capability pillars (Landing 3)
rasheedamir Aug 13, 2026
8340c6e
Revert "docs: restructure into capability pillars (Landing 3)"
rasheedamir Aug 13, 2026
8b8b957
Merge remote-tracking branch 'origin/main' into docs-overhaul-overview
rasheedamir Aug 13, 2026
49ce635
docs: add Deployment Models and rework Use Cases around customer situ…
rasheedamir Aug 13, 2026
b5b3843
docs: complete the Learn section and correct an unsupported FinOps claim
rasheedamir Aug 13, 2026
b6bcbb3
docs: make the assembly cost of choosing another tool explicit
rasheedamir Aug 13, 2026
e83042a
docs: stop ruling MTO out when tenants are external or untrusted
rasheedamir Aug 14, 2026
ddb81b3
docs: stop defining MTO as "a tenancy operator that also does X"
rasheedamir Aug 14, 2026
6711b1d
docs: surface capabilities that were documented but invisible to buyers
rasheedamir Aug 14, 2026
a7cd759
docs: stop endorsing the build path, and give its real scale
rasheedamir Aug 14, 2026
add9331
docs: date the maturity claim — continuous development since December…
rasheedamir Aug 14, 2026
dae72f6
docs: state that every extension is optional and inert until configured
rasheedamir Aug 14, 2026
c8e0ebf
docs: add the first production evidence — 100+ tenants, 700+ namespaces
rasheedamir Aug 14, 2026
b4e7a4f
docs: sharpen the customer evidence — single cluster, public sector
rasheedamir Aug 14, 2026
f790c7d
Merge branch 'main' into docs/console-admin-access
rasheedamir Aug 14, 2026
bfb987c
docs: fix Vale errors reported by the newer vale-package
rasheedamir Aug 14, 2026
43bae01
chore: bump the Vale package pin to v0.0.104
rasheedamir Aug 14, 2026
a168613
docs: remove repeated tokens across keyword-block line breaks
rasheedamir Aug 14, 2026
248d426
docs: rewrite the kubectl plugin page and make CLI a nav leaf
rasheedamir Aug 14, 2026
c6c27b2
docs: correct kubectl-tenant install for the v1.0.0 release format
rasheedamir Aug 14, 2026
4308dcb
docs: keep Learn late, but give the Overview a path into it
rasheedamir Aug 14, 2026
93f46cf
docs: avoid 'Namespaced' in the kubectl plugin page
rasheedamir Aug 14, 2026
b8a4bf2
docs: correct the support tiers and fill out the pricing page
rasheedamir Aug 14, 2026
3ff64f7
docs: explain what the Enterprise licence is measured against
rasheedamir Aug 14, 2026
100dbbc
docs: drop the "Before you talk to sales" section from the pricing page
rasheedamir Aug 14, 2026
97763dc
docs: state the licence plainly — per cluster
rasheedamir Aug 14, 2026
51692b1
ci: stop the link checker failing on the community Slack link
rasheedamir Aug 14, 2026
dceed6a
docs: fix heading casing on the kubectl plugin page
rasheedamir Aug 14, 2026
8d48c92
docs: overhaul Getting Started, and fix a wrong field name in the uni…
rasheedamir Aug 15, 2026
ac4d1d3
docs: correct three claims in the uninstall pages against the API ref…
rasheedamir Aug 15, 2026
35de033
docs: surface the kubectl plugin on the landing page
rasheedamir Aug 16, 2026
853487d
docs: add OpenBao to the integrations overview list
rasheedamir Aug 16, 2026
b50dd81
chore: refresh console screenshots
stakater-user Aug 17, 2026
b31c6b8
Merge pull request #419 from stakater/docs/console-admin-access
Felix-Stakater Aug 17, 2026
8bf9ae0
Merge branch 'main' into docs-overhaul-overview
msafwankarim Aug 17, 2026
149b735
Update CONSOLE_URL in config.env
msafwankarim Aug 17, 2026
0896562
chore: refresh console screenshots
stakater-user Aug 17, 2026
ed69959
Merge pull request #421 from stakater/msafwankarim-patch-1
SyedaFatimaKazmi Aug 18, 2026
c47845d
retain ordering of menu when merging
msafwankarim Aug 19, 2026
e207b67
ignore slack from link check
msafwankarim Aug 19, 2026
c2d9e3e
update link in aws pricing
msafwankarim Aug 19, 2026
54c3985
remove kubecost link resulting in forbidden
msafwankarim Aug 19, 2026
1dbe680
chore: refresh console screenshots
stakater-user Aug 19, 2026
d4ba359
Merge pull request #422 from stakater/docs/structure-migration
msafwankarim Aug 19, 2026
d9a2ba7
Merge branch 'main' into docs-overhaul-overview
rasheedamir Aug 19, 2026
9e7ed1a
Merge pull request #418 from stakater/docs-overhaul-overview
msafwankarim Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/md_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
},
{
"pattern": "^https://docs\\.redhat\\.com/en/documentation/openshift_container_platform/4\\.18/html/schedule_and_quota_apis/clusterresourcequota-quota-openshift-io-v1$"
},
{
"pattern": "^https://stakater-community\\.slack\\.com/"
}
],
"retryOn429": true
Expand Down
2 changes: 1 addition & 1 deletion .vale.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
StylesPath = styles
MinAlertLevel = warning

Packages = https://github.com/stakater/vale-package/releases/download/v0.0.93/Stakater.zip
Packages = https://github.com/stakater/vale-package/releases/download/v0.0.104/Stakater.zip
Vocab = Stakater

# Only check MarkDown files
Expand Down
25 changes: 22 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
VENV := .venv
PY := $(VENV)/bin/python

# Where local clones of the sub-operator docs repos live, used by the *-local
# targets so a preview does not re-clone. Each repo is expected at
# $(SUBOPS)/<repo name from merge.yaml>, e.g. ~/Documents/work/template-operator-docs.
SUBOPS ?= $(HOME)/Documents/work

.DEFAULT_GOAL := help
.PHONY: help venv test merge screenshots screenshots-check docs-images serve clean
.PHONY: help venv test theme merge merge-local screenshots screenshots-check docs-images serve serve-local clean

help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) \
| awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-10s\033[0m %s\n", $$1, $$2}'
| awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}'

venv: ## Create the local virtualenv if missing
@test -x $(PY) || python3 -m venv $(VENV)
Expand All @@ -27,6 +32,15 @@ test: venv ## Run the merge_docs unit tests
merge: ## Clone sub-operator repos and merge their docs (the CI pre-build hook)
bash scripts/pre_build_merge.sh

# Same merge, but against checkouts you already have, so no network and no clone.
# Whatever those checkouts have committed is what you preview, branch included.
merge-local: venv ## Merge from local sub-operator checkouts under SUBOPS (no cloning)
@args=$$($(PY) -c "import sys; sys.path.insert(0,'scripts'); import merge_docs; \
print(' '.join('--set-repo %s=$(SUBOPS)/%s' % (o['slug'], o['repo'].rstrip('/').split('/')[-1].removesuffix('.git')) \
for o in merge_docs.load_config('merge.yaml')))"); \
echo ">> merging from $(SUBOPS)"; \
$(PY) scripts/merge_docs.py $$args

# The build resolves {{ screenshot: ... }} itself (see screenshots/mkdocs_hook.py).
screenshots: ## Capture live console screenshots into screenshots/captured/
bash screenshots/capture.sh
Expand All @@ -41,14 +55,19 @@ docs-images: merge ## CI pre-build hook: merge sub-operator docs, then capture s
bash screenshots/capture.sh
python3 screenshots/inject.py --check

serve: venv ## Full local preview: combine theme, merge sub-operator docs, mkdocs serve
theme: venv ## Combine the shared theme with theme_override into dist/_theme and mkdocs.yml
git submodule update --init --recursive
$(VENV)/bin/pip install -q -r theme_common/requirements.txt
$(PY) theme_common/scripts/combine_theme_resources.py -s theme_common/resources -ov theme_override/resources -o dist/_theme
$(PY) theme_common/scripts/combine_mkdocs_config_yaml.py theme_common/mkdocs.yml theme_override/mkdocs.yml mkdocs.yml

serve: theme ## Full local preview: clones the sub-operator repos, then mkdocs serve
PYTHON=$(abspath $(PY)) bash scripts/pre_build_merge.sh
$(PY) -m mkdocs serve

serve-local: theme merge-local ## Same preview from local checkouts, no cloning
$(PY) -m mkdocs serve

clean: ## Remove fetched repos and generated artifacts (surgical; never `git clean`)
rm -rf .suboperators mkdocs.yml dist site
@python3 -c "import sys;sys.path.insert(0,'scripts');import merge_docs;[print(o['slug']) for o in merge_docs.load_config('merge.yaml')]" 2>/dev/null \
Expand Down
32 changes: 32 additions & 0 deletions PR_DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Merge sub-operator docs at build time

Adds a build-time tool that merges the docs of sub-operators (Template, Hibernation) into the MTO docs site, driven by a single `merge.yaml`. Sub-operator content is fetched in CI and never committed here.

## How it works

`scripts/merge_docs.py` reads `merge.yaml` and, per operator:

- **Copies** whitelisted files (glob `from` + `exclude`) into `content/<into>/<slug>/…` (the `<slug>` namespaces each operator, so pages never collide with MTO's own or each other; `flatten` keeps the slug but drops sub-folders → `content/<into>/<slug>/<file>`).
- **Builds nav** for the copied `.md` pages and nests it under an existing menu section (`under`), with folder titles taken from the sub-operator's own nav (falling back to title-case). With `flatten: true` the pages merge as **direct leaf entries** in the section (no operator wrapper, menu-flat); a `title` renames a single-file mapping (e.g. Template's API → one "Template Operator" entry). Splices only the `nav:` block of the combined `mkdocs.yml`, leaving the rest byte-for-byte.
- **Auto-groups duplicates**: when a page's filename appears in more than one source under the same flattened section (e.g. `argocd.md` in both MTO's own docs and Hibernation's), they're folded into a per-page folder titled by the page's H1 (→ `ArgoCD`), with one labelled leaf per source (`Multi-Tenant Operator`, `Hibernation Operator`). This is a rule, not a hand-maintained list — the labels come from operator titles and `site_title` (default: the mkdocs `site_name`).
- **Concatenates the API** (`concat_into` mapping mode): each operator's `reference/api.md` — plus MTO's own — merges into one **API Reference** page. Each source becomes an H2 section (`heading`, MTO's own labelled by `site_title`), its own headings demoted one level (code fences respected), so the in-page TOC lists `Tenant Operator` / `Template Operator` / `FinOps` / `Hibernation Operator`. Aggregated across operators by shared `concat_into`; one labelled leaf under `Reference`.
- **Rewrites links**: whitelisted targets → local relative links; anything else → the operator's published site (`live_url`, `directory`/`html` style), anchors preserved. Externalized links are logged.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LanguageTool] reported by reviewdog 🐶
File types are normally capitalized. (FILE_EXTENSIONS_CASE[1])
Suggestions: HTML
URL: https://languagetool.org/insights/post/spelling-capital-letters/
Rule: https://community.languagetool.org/rule/show/FILE_EXTENSIONS_CASE?lang=en-US&subId=1
Category: CASING


`merge.yaml` is the whitelist; the tool fails fast on empty globs, collisions, missing repos, or unknown menu sections.

## Integration

- **CI**: `pull_request.yaml` and `push.yaml` run `PRE_BUILD_HOOK: make merge` after theme-combine, before `mike deploy` (shared workflows at `v0.0.189`). `scripts/pre_build_merge.sh` clones each repo (token-aware) and runs the merge. `release.yaml` only containerizes the already-built `gh-pages`, so it needs no hook.
- **Local**: `make serve` (combine + merge + `mkdocs serve`), `make merge`, `make clean`.
- `DockerfileLocal` gains an optional, arg-gated merge step (skipped when unset).

## Verified

- `mkdocs build --strict` passes (0 warnings) against the live `main` branches of both sub-operators.
- 62 unit tests (path/glob/nav/flatten/duplicate-grouping/page-merging/link-rewriting/title-derivation).

## Notes

- `merge.yaml` `into`/`under` target the current MTO nav; they collapse to ~1:1 pairs once MTO migrates to `structure.md`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
58 changes: 42 additions & 16 deletions content/cli/overview.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
# kubectl plugin
# kubectl-tenant plugin

A kubectl plugin that provides tenant-scoped access to cluster resources managed by Multi-Tenant Operator.
Tenant users work in ordinary `kubectl`. The problem is that Kubernetes gives them no way to see what they own.

## Overview
Ask a developer to list the storage classes their tenant may use, or the namespaces it owns, and Kubernetes offers two answers, both wrong. Without cluster-scoped `list` permission they see nothing. With it they see everything on the cluster — every other tenant's namespaces included — because RBAC on a cluster-scoped resource is all-or-nothing. There is no "list the ones that are mine".

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LanguageTool] reported by reviewdog 🐶
Consider adding a comma here to clarify the structure of the sentence. (PRP_COMMA[1])
Suggestions: it,
Rule: https://community.languagetool.org/rule/show/PRP_COMMA?lang=en-US&subId=1
Category: PUNCTUATION


Kubernetes RBAC has a fundamental limitation: when granting `list` permissions on cluster-scoped resources, users can see *all* resources of that type, not just those belonging to their tenant. The kubectl-tenant plugin solves this by reading the Tenant CR status and filtering results to show only resources the tenant is permitted to access.
The `kubectl-tenant` plugin adds that missing verb.

**Source Code:** [https://github.com/stakater/kubectl-tenant](https://github.com/stakater/kubectl-tenant)
![kubectl tenant RBAC demo](../images/kubectlTenantRbacDemo.gif)

**Source:** [`kubectl-tenant` on GitHub](https://github.com/stakater/kubectl-tenant)

## Why It Matters

**For tenant users** — the platform becomes discoverable from the command line they already use. `kubectl tenant get namespaces my-tenant` answers "what do I have?" without a console, a ticket, or a guess. Every standard output flag works, so it composes with the scripts and pipelines already in use.

**For platform administrators** — you no longer choose between a usable platform and a tight one. Tenant users get a scoped view without being granted cluster-wide read, which is the permission you did not want to hand out and the one that quietly turns a multi-tenant cluster into a transparent one.

**Nothing in the request path.** The plugin runs on the user's machine: it reads the Tenant resource, works out what that tenant is entitled to, and asks the API server for those objects. It is not a proxy. Other approaches to this problem place a component between users and the API server, which is another deployment to run, scale, secure and keep available — and which every `kubectl` call then depends on. Here, if the plugin is absent, `kubectl` behaves exactly as it always did.

## Installation

Download the binary for your platform from [GitHub Releases](https://github.com/stakater/kubectl-tenant/releases):
Releases are published as archives for Linux, macOS and Windows on `amd64`, `arm64` and 32-bit architectures. Pick the one matching your machine from [GitHub Releases](https://github.com/stakater/kubectl-tenant/releases):

```bash
# Download for your OS/Arch
curl -L https://github.com/stakater/kubectl-tenant/releases/download/v0.0.1/kubectl-tenant-linux-amd64 -o kubectl-tenant
VERSION=v1.0.0
OS=linux # linux | darwin | windows
ARCH=amd64 # amd64 | arm64 | 386

curl -sL "https://github.com/stakater/kubectl-tenant/releases/download/${VERSION}/kubectl-tenant_${VERSION}_${OS}_${ARCH}.tar.gz" \
| tar -xz kubectl-tenant
chmod +x kubectl-tenant
mv kubectl-tenant ~/.local/bin/ # ensure this path is in your $PATH
```

Verify it works:
Each release also publishes a checksums file if you want to verify the download before installing.

`kubectl` discovers any executable named `kubectl-*` on your `PATH` and exposes it as a nested command, so no further configuration is needed. Verify it:

```bash
kubectl tenant --help
Expand All @@ -36,6 +52,8 @@ mv kubectl-tenant /usr/local/bin/

## Supported Resources

These are the cluster-scoped resources a tenant has an entitlement to, so they are the ones where "show me mine" is a question Kubernetes cannot answer on its own.

| Resource | Command Keyword |
|----------|----------------|
| Storage Classes | `storageclasses` |
Expand All @@ -44,6 +62,8 @@ mv kubectl-tenant /usr/local/bin/
| Priority Classes | `priorityclasses` |
| Quotas | `quotas` |

Resources that live inside a namespace are unaffected — ordinary `kubectl` already scopes those correctly through namespace RBAC.

## Usage

### Command Syntax
Expand All @@ -67,7 +87,7 @@ kubectl tenant get <resource-type> <tenant-name> <resource-name> [flags]

### List Tenants

List all tenants the current user belongs to (as owner, editor, or viewer):
Start here. It answers "which tenants am I in, and as what?" — useful when someone belongs to several, and the first thing to run after installing:

```bash
kubectl tenant list
Expand All @@ -79,6 +99,8 @@ logistics owner
warehouse viewer
```

The role shown is the tenant role that grants the access — owner, editor or viewer — so a user can see immediately why a command succeeds or is refused.

### List Resources

List all storage classes for a tenant:
Expand Down Expand Up @@ -121,7 +143,7 @@ my-tenant-fast kubernetes.io/aws-ebs 30d

### Output Formats

All standard kubectl output formats and flags are supported:
All standard kubectl output formats and flags are supported, so the plugin composes with existing tooling rather than replacing it:

```bash
# YAML output
Expand All @@ -139,10 +161,14 @@ kubectl tenant get namespaces my-tenant -o custom-columns=NAME:.metadata.name,ST

## How It Works

- Reads the specified Tenant CR from `tenantoperator.stakater.com/v1beta3`
- Extracts permitted resources from the tenant's status fields
- Fetches and returns only those resources the tenant can access
1. Reads the specified Tenant resource from `tenantoperator.stakater.com/v1beta3`.
1. Extracts the permitted resources from the tenant's status fields — the same status MTO's controllers maintain, so the answer reflects the live tenant definition rather than a cached copy.
1. Requests those objects from the API server and prints them.

## Demo
The user's own credentials are used throughout. The plugin narrows what is asked for; it does not widen what the user may have. Someone who is not a member of a tenant gets nothing from it.

![kubectl tenant RBAC demo](../images/kubectlTenantRbacDemo.gif)
## Next

- [Tenant](../concepts/tenant.md) — the resource the plugin reads
- [Console](../console/overview.md) — the same information, for people who prefer a UI
- [Storage Classes](../guides/storage-classes.md) and [Pod Priority Classes](../guides/pod-priority-classes.md) — how the entitlements it lists are configured
Loading
Loading