Show only the APIs that make sense for each context#567
Merged
Conversation
Filter kubectl discovery per parent context (Root / Organization / Project / User) so callers only see the resources relevant to where they are. Tag resources with a CRD annotation; the filter handles the rest. External CRDs participate the same way. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
ecv
previously approved these changes
Apr 16, 2026
|
thanks!! |
Rename the "Root" parent context to "Platform" for clarity — it represents the platform-level scope, not a generic cluster root. Annotate all 32 CRD types with discovery.miloapis.com/parent-contexts so the filter covers every Milo-managed resource. Two Notes CRDs are intentionally left unannotated (visible in all contexts). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Platform (root) context is where controllers, admin tools, and internal clients operate. Filtering discovery there breaks the controller-manager because it can't find CRD types like OrganizationMembership. Skip filtering entirely for Platform context requests — only apply it when a user explicitly navigates into Organization, Project, or User scope. Remove "Platform" from all CRD annotations since the context is never filtered and the tag has no effect. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Platform annotations are still meaningful — they tell the filter to hide resources from Organization/Project/User contexts. Only the Platform context *request path* bypasses filtering (so controllers see everything); the annotation value is still used when filtering other contexts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ture flag The filter is alpha and disabled by default. Enable with: --feature-gates=DiscoveryContextFilter=true When disabled, the registry is not created, the filter middleware is skipped, and the CRD informer post-start hook is a no-op. Zero overhead for deployments that don't opt in. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The chainsaw discovery-context-filter test requires the gate to be on. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kevwilliams
approved these changes
Apr 17, 2026
scotwells
added a commit
that referenced
this pull request
Apr 17, 2026
## What changed When you run `datumctl api-resources`, the list of resources is now correctly filtered based on your current context — so you only see what's relevant to where you are (organization, project, or user). ## Why it wasn't working The previous release (#567) introduced context-aware discovery filtering. It worked correctly when tested via direct HTTP calls, but `datumctl api-resources` was still showing everything regardless of context. The root cause: the filter was checking the response `Content-Type` header to detect the discovery format. In standard Kubernetes, the server echoes back the requested format in `Content-Type` — but Milo returns plain `application/json`, so the filter never recognized the format and skipped filtering entirely. ## The fix kubectl always declares the format it wants via the request `Accept` header. The filter now checks that instead, which is both more reliable and simpler — no dependency on what the server echoes back. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.
Summary
When a user runs
datumctl api-resourcesagainst Milo, they see every API the platform exposes — even the ones that don't apply to where they are. A user browsing their organization sees platform-level APIs mixed in with organization-level ones. A user inside a project sees organization-level APIs they can't act on. It's noise.This PR lets a resource declare which parent contexts it belongs in (Root, Organization, Project, User), and Milo filters discovery responses accordingly — so
datumctl api-resourcesonly shows the APIs that make sense for whatever URL the caller is using.What the user sees
Before — same list everywhere:
After — scoped to context:
How someone opts a resource in
On a Go-defined type (Milo's own APIs):
On a CRD installed by an external service building on Milo:
That's the whole contract. No annotation = visible everywhere (so nothing changes until a team opts in). Multiple contexts are comma-separated. Details and the thinking behind the design live in
docs/architecture/discovery-contexts.md.Who this helps
kubectl api-resources— only the APIs relevant to the URL they're hitting.Scope
This is a discovery hint — it changes what the list endpoints return, not what the server accepts. A caller who already knows a GVR can still talk to it directly. Turning this into a hard boundary would mean pairing the filter with an admission check; that's deliberately out of scope for this change and called out in the doc.
Three existing Milo resources are opted in as the first examples:
Organization(Root),Project(Organization),OrganizationMembership(Organization + User). Everything else stays visible everywhere until its owner chooses to tag it.Follow-up: aggregated apiservers
This PR covers CRDs — Milo's own types and any CRD an external service installs. It does not yet cover the aggregated apiservers Milo already proxies (
activity.miloapis.com,quota.miloapis.com,search.miloapis.com,incidents.operations.miloapis.com,identity.miloapis.com). Those are a larger slice of the user-facing surface than CRDs, so coverage is important.The filter already captures aggregated responses — only the source-of-truth for the mapping is missing. A follow-up PR will add a second registry source that reads an
x-milo-parent-contextsOpenAPI extension from the aggregated OpenAPI doc each aggregated service already serves. Each aggregated service will get a small companion PR to emit the extension on its schemas. Sequencing this as a follow-up keeps each change small and reviewable.Test plan
go test ./pkg/server/discovery/...)task test:end-to-end -- discovery-context-filter)kubectl api-resourcesagainst a live dev cluster in all three contextsOrganization,Project,OrganizationMembershiprender correctly aftertask generate🤖 Generated with Claude Code