Skip to content

feat: add reverse downstream namespace lookup - #769

Draft
scotwells wants to merge 1 commit into
mainfrom
feat/downstream-namespace-reverse-lookup
Draft

feat: add reverse downstream namespace lookup#769
scotwells wants to merge 1 commit into
mainfrom
feat/downstream-namespace-reverse-lookup

Conversation

@scotwells

@scotwells scotwells commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Downstream clusters hold a project's projected resources under ns-<upstream-namespace-uid> namespaces. Anything reading that data back out needs the reverse — which upstream cluster and namespace does this belong to? Every consumer re-derives that from the meta.datumapis.com/* labels itself today, and each one independently decides what to do when the answer isn't known yet. That's the risk: "the cache hasn't synced" and "this belongs to nobody" are different claims, and a consumer that collapses them attributes a customer's data to no tenant at all — which, for anything tenant-scoped, publishes it where every operator can read it.

This adds a resolver alongside the existing strategy so the mapping is owned once, in both directions, with a fail-closed contract callers can't get wrong by accident: distinct retryable and terminal errors, neither of which may be read as "attribute to the platform". The index is built from upstream namespace informers, so no credential into the downstream cluster is needed and a compromised downstream site can't influence what a namespace resolves to. Entries are never removed and can be persisted across restarts, because records about a deleted namespace must keep resolving for their full retention window. Readiness gates on cache sync rather than on a listener being bound, and an index with no registered sources reports unsynced so a misconfigured deployment fails its probe instead of silently failing every lookup closed.

Additive — no existing behaviour changes.

Note

The first consumer is edge audit ingest in milo-os/activity#229, which carries a mirrored copy of this code with a TODO to delete it. It can't import this until a Milo release contains it, since taking the module today would pull k8s.io/kubernetes into an aggregated API server's build graph. Merging and releasing here is what removes the duplicate.

Test plan

  • go build ./... and go vet ./pkg/downstreamclient/... — both exit 0
  • go test ./pkg/downstreamclient/... -count=1 — 6 tests pass
  • A cold cache returns the retryable error and the same lookup returns the terminal one once synced; an index with no sources never reports synced
  • Entries survive a snapshot/restore round trip, and restore does not displace live entries
  • Cluster names round-trip through the label encoding, including path-style names containing /
  • Exercised end to end by the consumer's suite (real ClickHouse, NATS and mTLS) against its mirrored copy — re-run there once this is released and the copy becomes an import

Notes for review

#627 also touches this package (mappednamespace.go) for the controller-runtime v0.23 upgrade. No file overlap, but the new file imports controller-runtime/pkg/cache, so whichever lands second may want a look.

Downstream clusters see resources under ns-<upstream-namespace-uid>
namespaces. Consumers of downstream data — federated audit and event
ingestion in particular — need the opposite direction: given a
downstream namespace name, which upstream cluster and namespace does it
belong to? Every such consumer was left to re-derive that from the
meta.datumapis.com/* labels itself, and each one had to decide
independently what to do when the answer is not known yet.

Add UpstreamNamespaceResolver alongside MappedNamespaceResourceStrategy
so the mapping is owned once, in both directions, with a fail-closed
contract callers cannot get wrong by accident.

Key changes:
- UpstreamNamespaceResolver returns distinct ErrCacheNotSynced (retry)
  and ErrUpstreamNamespaceUnknown (terminal) errors, so a cold cache is
  never mistaken for an unknown tenant and misattributed
- RetainingNamespaceIndex builds the index from upstream namespace
  informers, so no credential into the downstream cluster is required
- Index entries are never removed and can be snapshotted and restored,
  because records about a deleted namespace must keep resolving for
  their full retention window
- HasSynced covers every registered source and reports false when none
  are registered, so readiness gates on cache sync rather than on a
  listener being bound
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant