deployment

package
v0.3.2 Latest Latest
Warning

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

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

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

View Source
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"
)
View Source
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

View Source
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")

	// ErrDeploymentUnitUnavailable reports a unit that cannot accept or resolve
	// 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

type Auditor interface {
	Record(context.Context, Event) error
}

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

type AuditorFunc func(context.Context, Event) error

AuditorFunc adapts a function into an Auditor.

func (AuditorFunc) Record

func (auditor AuditorFunc) Record(ctx context.Context, event Event) error

Record implements 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

func (store *MemoryStore) DeleteMove(ctx context.Context, tenantID types.TenantID) error

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

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) GetMove

func (store *MemoryStore) GetMove(ctx context.Context, tenantID types.TenantID) (Move, error)

GetMove returns a tenant's prepared move.

func (*MemoryStore) GetUnit

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

func WithAuditor(auditor Auditor) Option

WithAuditor sets the optional committed-change auditor.

func WithPolicy

func WithPolicy(policy Policy) Option

WithPolicy sets the host-owned placement policy.

type Policy

type Policy interface {
	Validate(context.Context, types.Tenant, types.DeploymentUnit) error
}

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 Resolver

type Resolver interface {
	Resolve(context.Context, types.Tenant) (types.DeploymentUnit, error)
}

Resolver resolves a tenant to its active deployment unit.

type SQLDialect

type SQLDialect = sqlutil.Dialect

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

func (store *SQLStore) CreateMove(ctx context.Context, move Move) (err error)

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

func (store *SQLStore) CreateUnit(ctx context.Context, unit types.DeploymentUnit) error

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

func (store *SQLStore) DeleteMove(ctx context.Context, tenantID types.TenantID) error

DeleteMove removes a prepared deployment move.

func (*SQLStore) DeleteMoveIfMatch

func (store *SQLStore) DeleteMoveIfMatch(ctx context.Context, move Move) (err error)

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

func (store *SQLStore) DeleteUnit(ctx context.Context, id types.DeploymentUnitID) (err error)

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) GetMove

func (store *SQLStore) GetMove(ctx context.Context, tenantID types.TenantID) (Move, error)

GetMove returns a tenant's prepared deployment move.

func (*SQLStore) GetUnit

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) ListUnits

func (store *SQLStore) ListUnits(ctx context.Context) (units []types.DeploymentUnit, err error)

ListUnits returns all deployment units in ID order.

func (*SQLStore) UpdateUnit

func (store *SQLStore) UpdateUnit(ctx context.Context, unit types.DeploymentUnit) (err error)

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

type SQLStoreOption func(*SQLStore) error

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 New

func New(store Store, opts ...Option) *Service

New creates a deployment service backed by store.

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

func (service *Service) CancelMove(ctx context.Context, tenantID types.TenantID) error

CancelMove discards a prepared move without altering the current assignment.

func (*Service) CreateUnit

func (service *Service) CreateUnit(ctx context.Context, unit types.DeploymentUnit) error

CreateUnit adds a deployment unit to the directory.

func (*Service) CutoverMove

func (service *Service) CutoverMove(ctx context.Context, tenant types.Tenant) (Assignment, error)

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

func (service *Service) DeleteUnit(ctx context.Context, id types.DeploymentUnitID) error

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

func (service *Service) UpdateUnit(ctx context.Context, unit types.DeploymentUnit) error

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.

Jump to

Keyboard shortcuts

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