DatabaseAdmin API Reference
The DatabaseAdmin module provides utilities for creating and managing per-tenant database users and schemas in PostgreSQL multi-tenant deployments.
Overview
Section titled “Overview”use cloacina::database::{Database, DatabaseAdmin, TenantConfig, TenantCredentials};DatabaseAdmin
Section titled “DatabaseAdmin”Administrative interface for tenant provisioning operations.
pub struct DatabaseAdmin { // Private fields}
impl DatabaseAdmin { /// Create a new database administrator pub fn new(database: Database) -> Self;
/// Create a complete tenant setup (schema + user + permissions + migrations) pub fn create_tenant(&self, tenant_config: TenantConfig) -> Result<TenantCredentials, AdminError>;
/// Remove a tenant (user + schema) pub fn remove_tenant(&self, schema_name: &str, username: &str) -> Result<(), AdminError>;}TenantConfig
Section titled “TenantConfig”Configuration for creating a new tenant.
pub struct TenantConfig { /// Schema name for the tenant (e.g., "tenant_acme") pub schema_name: String,
/// Username for the tenant's database user (e.g., "acme_user") pub username: String,
/// Password for the tenant user - empty string triggers auto-generation pub password: String,}TenantCredentials
Section titled “TenantCredentials”Credentials returned after tenant creation.
pub struct TenantCredentials { /// Username of the created tenant user pub username: String,
/// Password (either provided or auto-generated) pub password: String,
/// Schema name for the tenant pub schema_name: String,
/// Ready-to-use connection string for the tenant pub connection_string: String,}AdminError
Section titled “AdminError”Errors that can occur during database administration operations.
#[derive(Debug, thiserror::Error)]pub enum AdminError { #[error("Database error: {0}")] Database(#[from] diesel::result::Error),
#[error("Connection pool error: {0}")] Pool(#[from] diesel::r2d2::PoolError),
#[error("SQL execution error: {message}")] SqlExecution { message: String },
#[error("Invalid configuration: {message}")] InvalidConfig { message: String },}Examples
Section titled “Examples”Basic Tenant Creation
Section titled “Basic Tenant Creation”use cloacina::database::{Database, DatabaseAdmin, TenantConfig};
// Create admin connectionlet admin_db = Database::new( "postgresql://admin:admin_pass@localhost/cloacina", "cloacina", 10);let admin = DatabaseAdmin::new(admin_db);
// Create tenant with auto-generated passwordlet creds = admin.create_tenant(TenantConfig { schema_name: "tenant_acme".to_string(), username: "acme_user".to_string(), password: "".to_string(), // Empty = auto-generate})?;
println!("Created tenant:");println!(" Username: {}", creds.username);println!(" Password: {}", creds.password);println!(" Connection: {}", creds.connection_string);Custom Password
Section titled “Custom Password”// Create tenant with custom passwordlet creds = admin.create_tenant(TenantConfig { schema_name: "tenant_xyz".to_string(), username: "xyz_user".to_string(), password: "custom_secure_password".to_string(),})?;Remove Tenant
Section titled “Remove Tenant”// Remove tenant and all associated dataadmin.remove_tenant("tenant_xyz", "xyz_user")?;Password Security
Section titled “Password Security”Auto-Generated Passwords
Section titled “Auto-Generated Passwords”When password is an empty string, a cryptographically secure password is generated:
- Length: 32 characters
- Character Set: 62 characters — uppercase, lowercase, and digits only (symbols are excluded to avoid URL/connection-string escaping issues)
- Entropy: ~190 bits
- Generator: Uses
rand::rng()(a CSPRNG seeded from the OS) for cryptographic randomness
Password Handling
Section titled “Password Handling”- No Storage: Cloacina never stores passwords
- PostgreSQL Hashing: All passwords are hashed with SCRAM-SHA-256 by PostgreSQL
- Immediate Return: Credentials are returned to the admin for secure distribution
- One-Time View: The plaintext password is only available at creation time
Database Operations
Section titled “Database Operations”What create_tenant Does
Section titled “What create_tenant Does”The method performs these operations in a single transaction:
-
Create Schema
CREATE SCHEMA IF NOT EXISTS tenant_xyz -
Create User
CREATE USER xyz_user WITH PASSWORD '...' -
Grant Permissions
GRANT USAGE ON SCHEMA tenant_xyz TO xyz_user;GRANT CREATE ON SCHEMA tenant_xyz TO xyz_user;GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA tenant_xyz TO xyz_user;GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA tenant_xyz TO xyz_user;ALTER DEFAULT PRIVILEGES IN SCHEMA tenant_xyz GRANT ALL ON TABLES TO xyz_user;ALTER DEFAULT PRIVILEGES IN SCHEMA tenant_xyz GRANT ALL ON SEQUENCES TO xyz_user; -
Run Migrations
- Sets search_path to the tenant schema
- Executes all Cloacina migrations in that schema
What remove_tenant Does
Section titled “What remove_tenant Does”The method performs these operations in a single transaction:
- Revoke Permissions
- Drop User:
DROP USER IF EXISTS xyz_user - Drop Schema:
DROP SCHEMA IF EXISTS tenant_xyz CASCADE
Production callers: use the HTTP route, not this library call. Per CLOACI-T-0581, the server’s
DELETE /v1/tenants/{name}route performs a 4-step orchestrated teardown around this call: (1) revoke API keys for the tenant, (2) evict the tenant’sDefaultRunnerfromTenantRunnerCachewith a bounded drain (--tenant-deletion-drain-timeout-s, default 30s), (3) evict the tenant’sDatabasefromTenantDatabaseCache, then (4) callDatabaseAdmin::remove_tenant(this function). Each step emits a structured audit event with duration. The bare library call skips steps 1-3 and leaves in-process state stale. Use it directly only for testing or for one-shot scripts where nocloacina-serverinstance is holding tenant caches.
Requirements
Section titled “Requirements”PostgreSQL Requirements
Section titled “PostgreSQL Requirements”- Version: PostgreSQL 10+ (for SCRAM-SHA-256 password hashing)
- Admin Privileges: The admin user needs:
CREATEDBprivilegeCREATEROLEprivilege- Permission to create schemas
- Permission to grant privileges
Feature Requirements
Section titled “Feature Requirements”- Only available when using the
postgresfeature - Not available for SQLite deployments
Error Handling
Section titled “Error Handling”Common Errors
Section titled “Common Errors”-
User Already Exists
AdminError::SqlExecution { message: "Failed to create user 'xyz_user': ..." } -
Invalid Schema Name
AdminError::InvalidConfig { message: "Schema name cannot be empty" } -
Insufficient Privileges
AdminError::SqlExecution { message: "Failed to create schema: permission denied" }
Integration with DefaultRunner
Section titled “Integration with DefaultRunner”The credentials returned by DatabaseAdmin are designed to work seamlessly with DefaultRunner:
// Create tenantlet creds = admin.create_tenant(config)?;
// Use credentials with DefaultRunnerlet runner = DefaultRunner::with_schema( &creds.connection_string, &creds.schema_name).await?;
// Execute workflows with full isolationrunner.execute(workflow).await?;Best Practices
Section titled “Best Practices”- Secure Credential Storage: Store returned credentials in a secrets management system
- Audit Logging: Log tenant creation/deletion for compliance
- Connection String Parsing: Consider parsing the admin connection to build tenant connection strings
- Error Recovery: Wrap operations in proper error handling for production use
- Resource Limits: Monitor total connection count across all tenants
Python Bindings
Section titled “Python Bindings”The Database Admin API is also available in Python with identical functionality:
import cloaca
# Create adminadmin = cloaca.DatabaseAdmin("postgresql://admin:password@localhost/db")
# Create tenantconfig = cloaca.TenantConfig( schema_name="tenant_acme", username="acme_user", password="" # Auto-generate)credentials = admin.create_tenant(config)
# Use with Python runnerrunner = cloaca.DefaultRunner(credentials.connection_string)