DatabaseAdmin API Reference
The DatabaseAdmin module provides utilities for creating and managing per-tenant database users and schemas in PostgreSQL multi-tenant deployments.
use cloacina::database::{Database, DatabaseAdmin, TenantConfig, TenantCredentials};
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>;
}
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,
}
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,
}
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 },
}
use cloacina::database::{Database, DatabaseAdmin, TenantConfig};
// Create admin connection
let admin_db = Database::new(
"postgresql://admin:admin_pass@localhost/cloacina",
"cloacina",
10
);
let admin = DatabaseAdmin::new(admin_db);
// Create tenant with auto-generated password
let 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);
// Create tenant with custom password
let creds = admin.create_tenant(TenantConfig {
schema_name: "tenant_xyz".to_string(),
username: "xyz_user".to_string(),
password: "custom_secure_password".to_string(),
})?;
// Remove tenant and all associated data
admin.remove_tenant("tenant_xyz", "xyz_user")?;
When password is an empty string, a cryptographically secure password is generated:
- Length: 32 characters
- Character Set: 94 characters (uppercase, lowercase, digits, symbols)
- Entropy: ~202 bits
- Generator: Uses
rand::thread_rng()for cryptographic randomness
- 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
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
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
- Version: PostgreSQL 10+ (for SCRAM-SHA-256 password hashing)
- Admin Privileges: The admin user needs:
CREATEDBprivilegeCREATEROLEprivilege- Permission to create schemas
- Permission to grant privileges
- Only available when using the
postgresfeature - Not available for SQLite deployments
-
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" }
The credentials returned by DatabaseAdmin are designed to work seamlessly with DefaultRunner:
// Create tenant
let creds = admin.create_tenant(config)?;
// Use credentials with DefaultRunner
let runner = DefaultRunner::with_schema(
&creds.connection_string,
&creds.schema_name
).await?;
// Execute workflows with full isolation
runner.execute(workflow).await?;
- 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
The Database Admin API is also available in Python with identical functionality:
import cloaca
# Create admin
admin = cloaca.DatabaseAdmin("postgresql://admin:password@localhost/db")
# Create tenant
config = cloaca.TenantConfig(
schema_name="tenant_acme",
username="acme_user",
password="" # Auto-generate
)
credentials = admin.create_tenant(config)
# Use with Python runner
runner = cloaca.DefaultRunner(credentials.connection_string)