Documentation
¶
Overview ¶
Package deployment manages host-defined logical placement of tenants in deployment units. It persists control-plane mappings and controlled moves; hosts retain responsibility for connection routing, data movement, and jurisdiction-specific policy decisions.
Index ¶
- Constants
- Variables
- type Assignment
- type Auditor
- type AuditorFunc
- type Event
- type MemoryStore
- func (store *MemoryStore) CompareAndSwapAssignment(ctx context.Context, expected Assignment, updated Assignment) error
- func (store *MemoryStore) CreateAssignment(ctx context.Context, assignment Assignment) error
- func (store *MemoryStore) CreateMove(ctx context.Context, move Move) error
- func (store *MemoryStore) CreateUnit(ctx context.Context, unit types.DeploymentUnit) error
- func (store *MemoryStore) CutoverMove(ctx context.Context, expected Assignment, move Move, updated Assignment) error
- func (store *MemoryStore) DeleteMove(ctx context.Context, tenantID types.TenantID) error
- func (store *MemoryStore) DeleteMoveIfMatch(ctx context.Context, move Move) error
- func (store *MemoryStore) DeleteUnit(ctx context.Context, id types.DeploymentUnitID) error
- func (store *MemoryStore) DisableUnit(ctx context.Context, id types.DeploymentUnitID) (types.DeploymentUnit, bool, error)
- func (store *MemoryStore) GetAssignment(ctx context.Context, tenantID types.TenantID) (Assignment, error)
- func (store *MemoryStore) GetMove(ctx context.Context, tenantID types.TenantID) (Move, error)
- func (store *MemoryStore) GetUnit(ctx context.Context, id types.DeploymentUnitID) (types.DeploymentUnit, error)
- func (store *MemoryStore) ListAssignmentsByUnit(ctx context.Context, unitID types.DeploymentUnitID) ([]Assignment, error)
- func (store *MemoryStore) ListMovesByUnit(ctx context.Context, unitID types.DeploymentUnitID) ([]Move, error)
- func (store *MemoryStore) ListUnits(ctx context.Context) ([]types.DeploymentUnit, error)
- func (store *MemoryStore) UpdateUnit(ctx context.Context, unit types.DeploymentUnit) error
- type Move
- type Option
- type Policy
- type PolicyFunc
- type Resolver
- type SQLDialect
- type SQLStore
- func (store *SQLStore) CompareAndSwapAssignment(ctx context.Context, expected Assignment, updated Assignment) (err error)
- func (store *SQLStore) CreateAssignment(ctx context.Context, assignment Assignment) (err error)
- func (store *SQLStore) CreateMove(ctx context.Context, move Move) (err error)
- func (store *SQLStore) CreateUnit(ctx context.Context, unit types.DeploymentUnit) error
- func (store *SQLStore) CutoverMove(ctx context.Context, expected Assignment, move Move, updated Assignment) (err error)
- func (store *SQLStore) DeleteMove(ctx context.Context, tenantID types.TenantID) error
- func (store *SQLStore) DeleteMoveIfMatch(ctx context.Context, move Move) (err error)
- func (store *SQLStore) DeleteUnit(ctx context.Context, id types.DeploymentUnitID) (err error)
- func (store *SQLStore) DisableUnit(ctx context.Context, id types.DeploymentUnitID) (unit types.DeploymentUnit, changed bool, err error)
- func (store *SQLStore) GetAssignment(ctx context.Context, tenantID types.TenantID) (Assignment, error)
- func (store *SQLStore) GetMove(ctx context.Context, tenantID types.TenantID) (Move, error)
- func (store *SQLStore) GetUnit(ctx context.Context, id types.DeploymentUnitID) (types.DeploymentUnit, error)
- func (store *SQLStore) ListAssignmentsByUnit(ctx context.Context, unitID types.DeploymentUnitID) (assignments []Assignment, err error)
- func (store *SQLStore) ListMovesByUnit(ctx context.Context, unitID types.DeploymentUnitID) (moves []Move, err error)
- func (store *SQLStore) ListUnits(ctx context.Context) (units []types.DeploymentUnit, err error)
- func (store *SQLStore) UpdateUnit(ctx context.Context, unit types.DeploymentUnit) (err error)
- type SQLStoreOption
- type Service
- func (service *Service) Assign(ctx context.Context, tenant types.Tenant, unitID types.DeploymentUnitID) (Assignment, error)
- func (service *Service) CancelMove(ctx context.Context, tenantID types.TenantID) error
- func (service *Service) CreateUnit(ctx context.Context, unit types.DeploymentUnit) error
- func (service *Service) CutoverMove(ctx context.Context, tenant types.Tenant) (Assignment, error)
- func (service *Service) DeleteUnit(ctx context.Context, id types.DeploymentUnitID) error
- func (service *Service) DisableUnit(ctx context.Context, id types.DeploymentUnitID) (types.DeploymentUnit, error)
- func (service *Service) PrepareMove(ctx context.Context, tenant types.Tenant, targetID types.DeploymentUnitID) (Move, error)
- func (service *Service) Resolve(ctx context.Context, tenant types.Tenant) (types.DeploymentUnit, error)
- func (service *Service) UpdateUnit(ctx context.Context, unit types.DeploymentUnit) error
- type Store
Constants ¶
const ( // DefaultSQLUnitTableName is the default deployment-unit directory table. DefaultSQLUnitTableName = "saas_deployment_units" // DefaultSQLAssignmentTableName is the default current tenant-placement table. DefaultSQLAssignmentTableName = "saas_tenant_deployments" // DefaultSQLMoveTableName is the default prepared tenant-move table. DefaultSQLMoveTableName = "saas_deployment_moves" )
const ( // SQLDialectMySQL uses question-mark placeholders and is the default. SQLDialectMySQL = sqlutil.DialectMySQL // SQLDialectSQLite uses question-mark placeholders. SQLDialectSQLite = sqlutil.DialectSQLite // SQLDialectPostgres uses numbered placeholders. SQLDialectPostgres = sqlutil.DialectPostgres )
Variables ¶
var ( // ErrNilStore reports that a deployment service has no backing store. ErrNilStore = errors.New("saas/deployment: nil store") // ErrNilDB reports that a SQL store was created with a nil database handle. ErrNilDB = errors.New("saas/deployment: nil db") // ErrInvalidTableName reports an unsafe SQL table name. ErrInvalidTableName = errors.New("saas/deployment: invalid table name") // ErrUnsupportedSQLDialect reports an unsupported SQL store dialect. ErrUnsupportedSQLDialect = errors.New("saas/deployment: unsupported sql dialect") // ErrInvalidDeploymentUnit reports invalid deployment unit metadata. ErrInvalidDeploymentUnit = errors.New("saas/deployment: invalid deployment unit") // ErrDeploymentUnitNotFound reports a missing deployment unit. ErrDeploymentUnitNotFound = errors.New("saas/deployment: deployment unit not found") // ErrDeploymentUnitAlreadyExists reports a duplicate deployment unit ID. ErrDeploymentUnitAlreadyExists = errors.New("saas/deployment: deployment unit already exists") // ErrDeploymentUnitConflict reports a concurrent deployment unit replacement. ErrDeploymentUnitConflict = errors.New("saas/deployment: deployment unit conflict") // tenant traffic because it is not active. ErrDeploymentUnitUnavailable = errors.New("saas/deployment: deployment unit unavailable") // ErrDeploymentUnitInUse reports an attempt to disable or delete a unit // referenced by a current assignment or prepared move. ErrDeploymentUnitInUse = errors.New("saas/deployment: deployment unit in use") // ErrInvalidAssignment reports an invalid tenant deployment assignment. ErrInvalidAssignment = errors.New("saas/deployment: invalid assignment") // ErrAssignmentNotFound reports a tenant without a deployment assignment. ErrAssignmentNotFound = errors.New("saas/deployment: assignment not found") // ErrAssignmentAlreadyExists reports an attempt to create a second initial // assignment for a tenant. ErrAssignmentAlreadyExists = errors.New("saas/deployment: assignment already exists") // ErrAssignmentConflict reports that an assignment changed after it was read. ErrAssignmentConflict = errors.New("saas/deployment: assignment conflict") // ErrInvalidMove reports an invalid deployment move request. ErrInvalidMove = errors.New("saas/deployment: invalid move") // ErrMoveNotFound reports a tenant without a prepared move. ErrMoveNotFound = errors.New("saas/deployment: move not found") // ErrMoveAlreadyExists reports an attempt to prepare a second move for a tenant. ErrMoveAlreadyExists = errors.New("saas/deployment: move already exists") // ErrMoveConflict reports an assignment that no longer matches a prepared move. ErrMoveConflict = errors.New("saas/deployment: move conflict") // ErrPolicyDenied reports a host policy rejecting a deployment unit. ErrPolicyDenied = errors.New("saas/deployment: policy denied") )
Functions ¶
This section is empty.
Types ¶
type Assignment ¶
type Assignment struct {
TenantID types.TenantID
UnitID types.DeploymentUnitID
Version uint64
}
Assignment is the current deployment unit selected for a tenant. Version is incremented on every cutover and supports compare-and-swap persistence.
type Auditor ¶
Auditor observes committed deployment control-plane changes. If Record returns an error, the change remains committed and the service returns the error to the caller so the audit failure is visible.
type AuditorFunc ¶
AuditorFunc adapts a function into an Auditor.
type Event ¶
type Event struct {
Action string
TenantID types.TenantID
UnitID types.DeploymentUnitID
SourceUnitID types.DeploymentUnitID
TargetUnitID types.DeploymentUnitID
}
Event describes a committed deployment control-plane change.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is a thread-safe Store for tests, examples, and single-process host integrations.
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore creates an empty in-memory deployment store.
func (*MemoryStore) CompareAndSwapAssignment ¶
func (store *MemoryStore) CompareAndSwapAssignment(ctx context.Context, expected Assignment, updated Assignment) error
CompareAndSwapAssignment replaces an assignment only if it still equals expected. Expected and updated must retain the same tenant ID.
func (*MemoryStore) CreateAssignment ¶
func (store *MemoryStore) CreateAssignment(ctx context.Context, assignment Assignment) error
CreateAssignment creates a tenant's initial deployment assignment.
func (*MemoryStore) CreateMove ¶
func (store *MemoryStore) CreateMove(ctx context.Context, move Move) error
CreateMove creates a prepared move for a tenant.
func (*MemoryStore) CreateUnit ¶
func (store *MemoryStore) CreateUnit(ctx context.Context, unit types.DeploymentUnit) error
CreateUnit inserts a deployment unit.
func (*MemoryStore) CutoverMove ¶
func (store *MemoryStore) CutoverMove(ctx context.Context, expected Assignment, move Move, updated Assignment) error
CutoverMove atomically replaces expected with updated and removes the exact prepared move. It keeps a cancelled or replaced move from changing the current assignment after the caller has observed it.
func (*MemoryStore) DeleteMove ¶
DeleteMove removes a tenant's prepared move.
func (*MemoryStore) DeleteMoveIfMatch ¶
func (store *MemoryStore) DeleteMoveIfMatch(ctx context.Context, move Move) error
DeleteMoveIfMatch removes a prepared move only when it still equals move.
func (*MemoryStore) DeleteUnit ¶
func (store *MemoryStore) DeleteUnit(ctx context.Context, id types.DeploymentUnitID) error
DeleteUnit removes an existing deployment unit.
func (*MemoryStore) DisableUnit ¶
func (store *MemoryStore) DisableUnit(ctx context.Context, id types.DeploymentUnitID) (types.DeploymentUnit, bool, error)
DisableUnit makes an unreferenced deployment unit unavailable. It returns whether the status changed and is idempotent for a disabled unit.
func (*MemoryStore) GetAssignment ¶
func (store *MemoryStore) GetAssignment(ctx context.Context, tenantID types.TenantID) (Assignment, error)
GetAssignment returns the current deployment assignment for a tenant.
func (*MemoryStore) GetUnit ¶
func (store *MemoryStore) GetUnit(ctx context.Context, id types.DeploymentUnitID) (types.DeploymentUnit, error)
GetUnit returns one deployment unit.
func (*MemoryStore) ListAssignmentsByUnit ¶
func (store *MemoryStore) ListAssignmentsByUnit(ctx context.Context, unitID types.DeploymentUnitID) ([]Assignment, error)
ListAssignmentsByUnit returns current assignments for a deployment unit in tenant ID order.
func (*MemoryStore) ListMovesByUnit ¶
func (store *MemoryStore) ListMovesByUnit(ctx context.Context, unitID types.DeploymentUnitID) ([]Move, error)
ListMovesByUnit returns prepared moves referencing a deployment unit in tenant ID order.
func (*MemoryStore) ListUnits ¶
func (store *MemoryStore) ListUnits(ctx context.Context) ([]types.DeploymentUnit, error)
ListUnits returns all deployment units in ID order.
func (*MemoryStore) UpdateUnit ¶
func (store *MemoryStore) UpdateUnit(ctx context.Context, unit types.DeploymentUnit) error
UpdateUnit replaces mutable metadata on an existing deployment unit. Status transitions must use DisableUnit so a stale metadata write cannot re-enable a unit changed by another service instance.
type Move ¶
type Move struct {
TenantID types.TenantID
SourceUnitID types.DeploymentUnitID
TargetUnitID types.DeploymentUnitID
}
Move is a prepared, not-yet-cut-over tenant placement change.
type Option ¶
type Option func(*Service)
Option configures a deployment service.
func WithAuditor ¶
WithAuditor sets the optional committed-change auditor.
func WithPolicy ¶
WithPolicy sets the host-owned placement policy.
type Policy ¶
Policy approves a tenant's use of a deployment unit. Hosts can use it to enforce data-residency, contractual, or other placement rules.
type PolicyFunc ¶
PolicyFunc adapts a function into a Policy.
func (PolicyFunc) Validate ¶
func (policy PolicyFunc) Validate(ctx context.Context, tenant types.Tenant, unit types.DeploymentUnit) error
Validate implements Policy.
type SQLDialect ¶
SQLDialect controls SQL placeholder rendering for SQLStore.
type SQLStore ¶
type SQLStore struct {
// contains filtered or unexported fields
}
SQLStore persists deployment units, tenant assignments, and prepared moves through database/sql. The host owns the schema and migrations.
The unit table requires: id, status, region, residency_tags, metadata. The assignment table requires: tenant_id, deployment_unit_id, version. The move table requires: tenant_id, source_unit_id, target_unit_id.
func NewSQLStore ¶
func NewSQLStore(db *sql.DB, opts ...SQLStoreOption) (*SQLStore, error)
NewSQLStore creates a SQL-backed deployment store.
func (*SQLStore) CompareAndSwapAssignment ¶
func (store *SQLStore) CompareAndSwapAssignment(ctx context.Context, expected Assignment, updated Assignment) (err error)
CompareAndSwapAssignment replaces an assignment when its source unit and version still equal expected. Service callers should use CutoverMove for a placement change so the prepared move is removed in the same transaction.
func (*SQLStore) CreateAssignment ¶
func (store *SQLStore) CreateAssignment(ctx context.Context, assignment Assignment) (err error)
CreateAssignment creates a tenant's initial deployment assignment. It locks the target unit and verifies that the unit is active before inserting, which serializes the operation with disable and delete transitions.
func (*SQLStore) CreateMove ¶
CreateMove records a prepared deployment move. It locks the active target unit and current assignment before inserting, which prevents a stale source assignment or concurrent unit disable from producing an invalid move.
func (*SQLStore) CreateUnit ¶
CreateUnit inserts a deployment unit.
func (*SQLStore) CutoverMove ¶
func (store *SQLStore) CutoverMove(ctx context.Context, expected Assignment, move Move, updated Assignment) (err error)
CutoverMove atomically updates an assignment and removes the exact prepared move. The unit, assignment, and move are locked inside one transaction so a concurrent cancellation or lifecycle transition cannot leave a split state.
func (*SQLStore) DeleteMove ¶
DeleteMove removes a prepared deployment move.
func (*SQLStore) DeleteMoveIfMatch ¶
DeleteMoveIfMatch removes a prepared move only when it still equals move. It prevents a stale cancellation request from deleting a replacement move.
func (*SQLStore) DeleteUnit ¶
DeleteUnit removes an unreferenced deployment unit. The row lock used here is also acquired by assignment and move creation for the referenced unit.
func (*SQLStore) DisableUnit ¶
func (store *SQLStore) DisableUnit(ctx context.Context, id types.DeploymentUnitID) (unit types.DeploymentUnit, changed bool, err error)
DisableUnit makes an unreferenced deployment unit unavailable. It locks the unit before checking references, so concurrent placement writes cannot bind a tenant after the in-use check and before the status transition.
func (*SQLStore) GetAssignment ¶
func (store *SQLStore) GetAssignment(ctx context.Context, tenantID types.TenantID) (Assignment, error)
GetAssignment returns the current assignment for a tenant.
func (*SQLStore) GetUnit ¶
func (store *SQLStore) GetUnit(ctx context.Context, id types.DeploymentUnitID) (types.DeploymentUnit, error)
GetUnit returns one deployment unit.
func (*SQLStore) ListAssignmentsByUnit ¶
func (store *SQLStore) ListAssignmentsByUnit(ctx context.Context, unitID types.DeploymentUnitID) (assignments []Assignment, err error)
ListAssignmentsByUnit returns assignments in tenant ID order.
func (*SQLStore) ListMovesByUnit ¶
func (store *SQLStore) ListMovesByUnit(ctx context.Context, unitID types.DeploymentUnitID) (moves []Move, err error)
ListMovesByUnit returns moves that use a unit as source or target in tenant ID order.
func (*SQLStore) UpdateUnit ¶
UpdateUnit replaces mutable deployment-unit metadata. Status transitions are rejected here and must use DisableUnit, preventing a stale metadata write from re-enabling a unit changed by another service instance.
type SQLStoreOption ¶
SQLStoreOption configures SQLStore.
func WithAssignmentTableName ¶
func WithAssignmentTableName(table string) SQLStoreOption
WithAssignmentTableName overrides the tenant-placement table name.
func WithMoveTableName ¶
func WithMoveTableName(table string) SQLStoreOption
WithMoveTableName overrides the prepared tenant-move table name.
func WithSQLDialect ¶
func WithSQLDialect(dialect SQLDialect) SQLStoreOption
WithSQLDialect configures SQL placeholder rendering.
func WithUnitTableName ¶
func WithUnitTableName(table string) SQLStoreOption
WithUnitTableName overrides the deployment-unit directory table name.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service manages deployment units and tenant placement changes.
func (*Service) Assign ¶
func (service *Service) Assign(ctx context.Context, tenant types.Tenant, unitID types.DeploymentUnitID) (Assignment, error)
Assign records a tenant's initial deployment unit. Existing assignments are intentionally immutable outside the prepared move workflow.
func (*Service) CancelMove ¶
CancelMove discards a prepared move without altering the current assignment.
func (*Service) CreateUnit ¶
CreateUnit adds a deployment unit to the directory.
func (*Service) CutoverMove ¶
CutoverMove atomically changes the current assignment from a prepared move's source to its target and clears the move through the Store's transactional cutover operation.
func (*Service) DeleteUnit ¶
DeleteUnit removes an unreferenced deployment unit.
func (*Service) DisableUnit ¶
func (service *Service) DisableUnit(ctx context.Context, id types.DeploymentUnitID) (types.DeploymentUnit, error)
DisableUnit makes an unreferenced deployment unit unavailable for new assignments and cutovers. It is idempotent for an already-disabled unit.
func (*Service) PrepareMove ¶
func (service *Service) PrepareMove(ctx context.Context, tenant types.Tenant, targetID types.DeploymentUnitID) (Move, error)
PrepareMove records a target deployment unit while resolution continues to use the current source assignment. The host performs data copying and validation before calling CutoverMove.
func (*Service) Resolve ¶
func (service *Service) Resolve(ctx context.Context, tenant types.Tenant) (types.DeploymentUnit, error)
Resolve returns the active deployment unit assigned to tenant.
func (*Service) UpdateUnit ¶
UpdateUnit replaces mutable deployment unit metadata. Status changes are reserved for DisableUnit so assignment and move safety checks cannot be bypassed.
type Store ¶
type Store interface {
GetUnit(ctx context.Context, id types.DeploymentUnitID) (types.DeploymentUnit, error)
ListUnits(ctx context.Context) ([]types.DeploymentUnit, error)
CreateUnit(ctx context.Context, unit types.DeploymentUnit) error
UpdateUnit(ctx context.Context, unit types.DeploymentUnit) error
DisableUnit(ctx context.Context, id types.DeploymentUnitID) (types.DeploymentUnit, bool, error)
DeleteUnit(ctx context.Context, id types.DeploymentUnitID) error
GetAssignment(ctx context.Context, tenantID types.TenantID) (Assignment, error)
ListAssignmentsByUnit(ctx context.Context, unitID types.DeploymentUnitID) ([]Assignment, error)
CreateAssignment(ctx context.Context, assignment Assignment) error
CompareAndSwapAssignment(ctx context.Context, expected Assignment, updated Assignment) error
CutoverMove(ctx context.Context, expected Assignment, move Move, updated Assignment) error
GetMove(ctx context.Context, tenantID types.TenantID) (Move, error)
ListMovesByUnit(ctx context.Context, unitID types.DeploymentUnitID) ([]Move, error)
CreateMove(ctx context.Context, move Move) error
DeleteMove(ctx context.Context, tenantID types.TenantID) error
DeleteMoveIfMatch(ctx context.Context, move Move) error
}
Store persists deployment units, current assignments, and prepared moves. Implementations must preserve the placement invariants across processes: assignment and move creation may reference only active units, disabling or deleting a unit must reject references, and CutoverMove must atomically replace the expected assignment and remove the exact prepared move.