multitenant

package
v0.72.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 15, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidTenantID = errors.New("multitenant: tenant id does not match the default grammar")

ErrInvalidTenantID reports a tenant identifier that does not match DefaultTenantIDPattern.

View Source
var ErrNoTenant = errors.New("multitenant: no tenant in context")

ErrNoTenant reports a context that carries no tenant. It is the error form of GetTenant's ok result, so every lane answers "no tenant" the same way.

View Source
var ErrTenantResolutionFailed = errors.New("tenant resolution failed")

ErrTenantResolutionFailed is returned when a resolver cannot determine the tenant identifier.

View Source
var ErrUntrustedForwardedHost = errors.New("multitenant: X-Forwarded-Host from an untrusted peer")

ErrUntrustedForwardedHost is returned by a SubdomainResolver with TrustProxies set when a peer it does not trust sends X-Forwarded-Host. CompositeResolver stops on it instead of trying the next sub-resolver.

Functions

func DefaultTenantIDPattern added in v0.61.0

func DefaultTenantIDPattern() *regexp.Regexp

DefaultTenantIDPattern returns the framework's tenant identifier grammar.

func FanOutRetentionCleanup added in v0.42.0

func FanOutRetentionCleanup(
	ctx context.Context,
	log logger.Logger,
	tenants []string,
	getDB func(context.Context) (dbtypes.Interface, error),
	retention time.Duration,
	name string,
	del RetentionDelete,
) error

FanOutRetentionCleanup runs a retention delete once per tenant, resolving each tenant's database via getDB with that tenant injected into the context (SetTenant). Per-tenant errors AND panics are collected with errors.Join so one unhealthy tenant cannot block the others. Single-tenant callers pass tenants=[""] (SetTenant("") is a no-op). name labels the job in log and error messages (e.g. "outbox", "inbox").

This is shared by the outbox and inbox cleanup jobs, whose Execute bodies are otherwise identical — only the store delete and the label differ.

func ForEachTenant added in v0.72.0

func ForEachTenant(ctx context.Context, tenants []string, fn func(ctx context.Context, tenantID string) error) error

ForEachTenant runs fn once per tenant, in the given order, each inside that tenant's own lease scope (ADR-032): the tenant's context carries the tenant (SetTenant; "" runs fn once with no tenant) and a fresh scope that shadows any enclosing request, message or job scope, drained when fn returns or panics. A tenant-sweeping job then holds about one tenant's leased handles at a time instead of every tenant's until the job returns.

ctx is checked before each tenant: once it is done no further tenant starts and ctx.Err() joins the result. Errors from fn are joined unwrapped (fn receives the tenant ID and owns its text) and the sweep continues. A panic in fn is not recovered; it propagates after that tenant's scope has drained. A nil or empty list returns nil.

Every borrow inside fn must use fn's ctx or a context derived from it: a borrow on the outer context lands in the enclosing scope and is not bounded. A handle, database.Session or transaction obtained inside fn must not be used after fn returns. Releasing a lease does not close a cached handle, so the bound matters only when a sweep covers more tenants than the manager's max size. The caller supplies the list: Config.PerTenantJobKeys() for static tenants, or its own list for dynamic sources.

func GetTenant

func GetTenant(ctx context.Context) (string, bool)

GetTenant extracts the tenant identifier from the context.

func SetTenant

func SetTenant(ctx context.Context, tenantID string) context.Context

SetTenant stores the tenant identifier in the provided context.

func TenantID added in v0.61.0

func TenantID(ctx context.Context) (string, error)

TenantID is GetTenant in error form: the tenant identifier, or ErrNoTenant.

func ValidateTenantID added in v0.61.0

func ValidateTenantID(id string) error

ValidateTenantID checks an identifier against DefaultTenantIDPattern.

SECURITY: the identifier is caller-written (an HTTP header, a message stamp), so the error carries its byte length only — never the value itself.

Types

type CompositeResolver

type CompositeResolver struct {
	Resolvers   []TenantResolver
	TenantRegex *regexp.Regexp // Optional validation pattern
}

CompositeResolver tries multiple resolvers until one succeeds. ErrUntrustedForwardedHost is terminal: it is returned at once and no later resolver is tried.

func (*CompositeResolver) ResolveTenant

func (r *CompositeResolver) ResolveTenant(ctx context.Context, req *http.Request) (string, error)

ResolveTenant implements TenantResolver.

type HeaderResolver

type HeaderResolver struct {
	HeaderName string
}

HeaderResolver extracts the tenant identifier from a configured request header.

func (*HeaderResolver) ResolveTenant

func (r *HeaderResolver) ResolveTenant(ctx context.Context, req *http.Request) (string, error)

ResolveTenant implements TenantResolver.

type PathResolver added in v0.34.0

type PathResolver struct {
	Segment int
	Prefix  string
}

PathResolver extracts the tenant identifier from a path segment.

Segment is 1-indexed (segment 1 is the first non-empty path part after the leading slash). When Prefix is non-empty, the resolver only attempts extraction for requests whose path equals Prefix exactly or starts with "Prefix/" — non-matching paths return ErrTenantResolutionFailed so the composite chain (if any) can fall through to other resolvers.

func (*PathResolver) ResolveTenant added in v0.34.0

func (r *PathResolver) ResolveTenant(ctx context.Context, req *http.Request) (string, error)

ResolveTenant implements TenantResolver.

type RetentionDelete added in v0.42.0

type RetentionDelete func(ctx context.Context, db dbtypes.Interface, cutoff time.Time) (int64, error)

RetentionDelete removes rows older than cutoff from the resolved tenant database. It is the per-vendor delete a retention-cleanup job runs (e.g. the outbox's DeletePublished or the inbox's DeleteProcessed) and matches those store methods' signatures directly.

type SubdomainResolver

type SubdomainResolver struct {
	RootDomain   string
	TrustProxies bool
	// TrustedProxies adds peer ranges trusted to send X-Forwarded-Host, on top of
	// loopback, link-local, private and unix-socket peers.
	TrustedProxies []*net.IPNet
}

SubdomainResolver extracts the tenant identifier from the request host.

func (*SubdomainResolver) ResolveTenant

func (r *SubdomainResolver) ResolveTenant(ctx context.Context, req *http.Request) (string, error)

ResolveTenant implements TenantResolver.

type TenantResolver

type TenantResolver interface {
	ResolveTenant(ctx context.Context, req *http.Request) (string, error)
}

TenantResolver resolves the tenant identifier from an incoming request.

type ValidatingResolver

type ValidatingResolver struct {
	Resolver    TenantResolver
	TenantRegex *regexp.Regexp
}

ValidatingResolver wraps any resolver with tenant ID validation.

func (*ValidatingResolver) ResolveTenant

func (r *ValidatingResolver) ResolveTenant(ctx context.Context, req *http.Request) (string, error)

ResolveTenant implements TenantResolver with validation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL