internal

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package internal is every implementation of the tenant module. Nothing outside modules/tenant can import it, which is the compiler enforcing idea 3.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Bootstrap

Bootstrap is the first tenant of an installation, created from the command line rather than from a request.

It refuses when any tenant already exists, and that refusal is the whole point: this is the one write that runs with no caller to authorize, so the condition that makes it safe is that it can only ever happen once. Two of these racing is one of them: kit/app.Bootstrap takes an advisory lock for the transaction before this reads the list.

The tenant it creates is the operator's — the installation's own, whose administrators may reach the control plane at all. This line is the only writer of that flag in the application: NewTenant.Operator is json:"-", so no request body carries one.

func RegisterRoutes

func RegisterRoutes(api *httpx.API, svc contracts.Service, invite contracts.Inviter, token tenancy.SystemToken)

RegisterRoutes mounts the five control-plane routes.

They are written by hand rather than mounted from a rest.Spec because a tenant is not a crud.Entity: it carries no tenant_id, so the generic repository — which stamps one from the transaction — has nothing to stamp. That is the whole cost of the exception, and it is five short handlers.

Every one of them opens a transaction of its own. The request already holds a tenant transaction, because recognising the caller was a query in it, and a system transaction cannot widen a tenant one: db.Detached is what says so out loud. The consequence is written down where it matters — a tenant created here is created whether or not the response afterwards reaches the caller.

Types

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service is the control plane. Its one field is the list of things main asked to happen inside a create, which is how the modules above this one are notified without this one importing them (see contracts.Hook).

func NewService

func NewService(hooks []contracts.Hook) *Service

NewService returns the control plane. module.go constructs it.

func (*Service) AddHost

func (s *Service) AddHost(ctx context.Context, tx db.Tx[db.System], id uuid.UUID, host string, primary bool) (*contracts.Tenant, error)

AddHost gives an existing tenant another name to answer at, and says whether it is the one to name. The same host again is the same tenant and no second event — but it is still promoted, because "make this the primary" is a thing somebody may ask about a host that is already there.

func (*Service) ByHost

func (s *Service) ByHost(_ context.Context, tx db.Tx[db.System], host string) (tenancy.Tenant, error)

ByHost is httpx.TenantLoader: the query every request makes before it is a request. A suspended tenant answers ErrNoSuchHost rather than a refusal, because from outside a site that is not served and a site that does not exist are the same fact, and saying which is telling a stranger about a customer.

func (*Service) Create

func (s *Service) Create(ctx context.Context, tx db.Tx[db.System], in contracts.NewTenant) (*contracts.Tenant, error)

Create writes the tenant, its first host and whatever the hooks add, all in the caller's transaction, so an installation is either whole or absent.

func (*Service) Get

func (s *Service) Get(_ context.Context, tx db.Tx[db.System], id uuid.UUID) (*contracts.Tenant, error)

Get is one tenant with its hosts.

func (*Service) Hosts

func (s *Service) Hosts(_ context.Context, tx db.Tx[db.Tenant]) ([]string, error)

Hosts are the names the transaction's own tenant is served at, the primary one first.

One query, under the tenant's own policy: tenant_hosts lets a tenant transaction see its own rows and nothing else, so this needs no capability and grants none. The order is the same as everywhere else here, so "the first host" means the same thing to a mailed link as it does to a screen — and it now means something a person chose, rather than whichever name sorts first.

func (*Service) List

func (s *Service) List(_ context.Context, tx db.Tx[db.System]) ([]*contracts.Tenant, error)

List is every tenant that is not deleted, with its hosts. The hosts come back in one query rather than one per tenant, because the control plane's list is read by a screen and a screen that costs a query per row is a screen nobody keeps.

func (*Service) Suspend

func (s *Service) Suspend(ctx context.Context, tx db.Tx[db.System], id uuid.UUID) (*contracts.Tenant, error)

Suspend stops the tenant being served. Suspending it again changes nothing and says nothing: an operator's retry must not appear twice in an audit.

Jump to

Keyboard shortcuts

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