Structum Bootstrap (structum-bootstrap)¶
Structum Bootstrap provides “Fail Fast” mechanisms to validate the execution environment at application startup.
Feature |
Status |
Version |
|---|---|---|
Status |
Alpha |
0.1.0 |
Namespace |
|
|
Dependencies |
|
Index¶
1. What is Bootstrap Plugin¶
structum-bootstrap implements the “Professional Paranoia” philosophy: explicit verification of runtime state before application startup.
1.1 The Problem¶
Before (Without Plugin):
# ❌ Implicit Assumptions
app.run()
# Runtime errors after deploy:
# - "DATABASE_URL not set"
# - "/var/log/app not writable"
# - "Python 3.8 required, found 3.7"
After (With Plugin):
# ✅ Explicit Validation
boot = SystemBootstrapper()
boot.add_validator(EnvValidator(required=["DATABASE_URL"]))
boot.add_validator(DirectoryValidator("/var/log/app", writable=True))
boot.add_validator(PythonVersionValidator(min_version="3.9"))
boot.run_or_exit() # Fails FAST with detailed report
# If we reach here, the environment is GUARANTEED valid
app.run()
1.2 Key Features¶
Feature |
Description |
|---|---|
Fail Fast |
Errors at startup, not runtime |
Detailed Reports |
Textual report with all checks |
Type Safe |
Protocol-based Validators |
Extensible |
Create custom validators easily |
Zero Secrets Leak |
Validates ENV existence without logging values |
Production Ready |
Filesystem, network, database connectivity validation |
1.3 Philosophy: Explicit > Implicit¶
graph TD
subgraph "Traditional Approach (Implicit)"
A["app.run"] --> B{"Check Runtime?"}
B -->|No| C["Assume DB_URL exists"]
B -->|No| D["Assume /logs writable"]
C & D --> E["Runtime Crash 💥"]
style E fill:#ffcccc,stroke:#cc0000
end
subgraph "Structum Approach (Explicit)"
X["boot.validate"] --> Y["Check Environment"]
Y --> Y1["Check DB_URL"]
Y --> Y2["Check /logs"]
Y --> Y3["Check Python Ver"]
Y1 & Y2 & Y3 -->|All Pass| Z["app.run GUARANTEED Safe ✅"]
Y1 & Y2 & Y3 -->|Fail| F["Exit Immediately with Report"]
style Z fill:#ccffcc,stroke:#006600
style F fill:#ffffcc,stroke:#aaaa00
end
2. Core Concepts¶
2.1 SystemBootstrapper¶
The central orchestrator that runs validators in sequence:
bootstrapper = SystemBootstrapper()
bootstrapper.add_validator(validator1)
bootstrapper.add_validator(validator2)
# Run validation
result = bootstrapper.run()
if not result.success:
print(result.report)
sys.exit(1)
2.2 Validator Protocol¶
Every validator implements this contract:
from typing import Protocol
class Validator(Protocol):
"""Protocol for custom validators."""
def validate(self, context: ValidationContext) -> None:
"""
Executes validation.
Records results in context.
"""
...
2.3 ValidationContext¶
Container for validation results:
context = ValidationContext()
# Record check success
context.add_check("Database", success=True, message="Connected OK")
# Record check failure
context.add_check("Redis", success=False, message="Connection timeout")
# Check status
if context.has_failures():
print(context.get_report())
3. Quick Start (5 Minutes)¶
Step 1: Installation¶
pip install -e packages/bootstrap
Step 2: Basic Validation¶
from structum_lab.plugins.bootstrap import (
SystemBootstrapper,
EnvValidator,
PythonVersionValidator
)
def main():
# 1. Create bootstrapper
boot = SystemBootstrapper()
# 2. Add validators
boot.add_validator(
EnvValidator(required=["DATABASE_URL", "API_KEY"])
)
boot.add_validator(
PythonVersionValidator(min_version="3.9")
)
# 3. Run validation
boot.run_or_exit() # Exits with code 1 if validation fails
# 4. Start application (guaranteed valid environment)
print("✅ Environment validated - starting application")
start_application()
if __name__ == "__main__":
main()
Step 3: Test Validation¶
# Missing env var - should fail
python main.py
# Output:
# ❌ Bootstrap Validation Failed
#
# Environment Variables:
# ✗ DATABASE_URL: Missing
# ✗ API_KEY: Missing
#
# Python Version:
# ✓ Version 3.11.0 >= 3.9 (required)
# Set env vars
export DATABASE_URL=postgresql://localhost/mydb
export API_KEY=secret-key-123
python main.py
# Output:
# ✅ Environment validated - starting application
4. Built-in Validators¶
4.1 EnvValidator¶
Validates that environment variables exist.
from structum_lab.plugins.bootstrap import EnvValidator
# Required vars
validator = EnvValidator(required=["DB_URL", "API_KEY"])
# Optional vars (warns if missing)
validator = EnvValidator(
required=["DB_URL"],
optional=["REDIS_URL", "CACHE_TTL"]
)
# Custom names for reporting
validator = EnvValidator(
required={
"DATABASE_URL": "Primary Database Connection",
"REDIS_URL": "Cache Server Connection"
}
)
Security Note: validator logs only presence, never values.
✓ DATABASE_URL: Present
✗ API_KEY: Missing
4.2 PythonVersionValidator¶
Validates Python runtime version.
from structum_lab.plugins.bootstrap import PythonVersionValidator
# Minimum version
validator = PythonVersionValidator(min_version="3.9")
# Exact version range
validator = PythonVersionValidator(
min_version="3.9",
max_version="3.11"
)
# Specific versions
validator = PythonVersionValidator(
allowed_versions=["3.10", "3.11"]
)
4.3 DirectoryValidator¶
Validates that directories exist and are writable.
from structum_lab.plugins.bootstrap import DirectoryValidator
# Check existence
validator = DirectoryValidator("/var/log/myapp")
# Check writable
validator = DirectoryValidator(
"/var/log/myapp",
writable=True
)
# Check readable
validator = DirectoryValidator(
"/etc/myapp",
readable=True
)
# Auto-create if missing
validator = DirectoryValidator(
"/var/log/myapp",
writable=True,
create_if_missing=True
)
4.4 ConfigValidator¶
Validates that Structum config loads correctly.
from structum_lab.plugins.bootstrap import ConfigValidator
# Basic validation
validator = ConfigValidator()
# Validate specific keys exist
validator = ConfigValidator(
required_keys=["database.url", "app.name"]
)
# Validate config provider
validator = ConfigValidator(
provider_class=DynaconfConfigProvider
)
4.5 DatabaseValidator¶
Validates database connectivity.
from structum_lab.plugins.bootstrap import DatabaseValidator
# Basic connectivity check
validator = DatabaseValidator(
url="postgresql://localhost/mydb"
)
# With timeout
validator = DatabaseValidator(
url="postgresql://localhost/mydb",
timeout=5.0 # seconds
)
# From config
validator = DatabaseValidator.from_config()
4.6 FileValidator¶
Validates existence and permissions of files.
from structum_lab.plugins.bootstrap import FileValidator
# Check file exists
validator = FileValidator("/etc/myapp/config.toml")
# Check readable
validator = FileValidator(
"/etc/myapp/config.toml",
readable=True
)
# Check specific size constraints
validator = FileValidator(
"/var/lib/myapp/data.db",
max_size_mb=1000
)
5. Custom Validators¶
5.1 Simple Custom Validator¶
from structum_lab.plugins.bootstrap import Validator, ValidationContext
class PortAvailableValidator(Validator):
"""Check if network port is available."""
def __init__(self, port: int):
self.port = port
def validate(self, context: ValidationContext) -> None:
import socket
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', self.port))
sock.close()
context.add_check(
f"Port {self.port}",
success=True,
message=f"Available"
)
except OSError:
context.add_check(
f"Port {self.port}",
success=False,
message=f"Already in use"
)
# Usage
boot.add_validator(PortAvailableValidator(8000))
5.2 Advanced Custom Validator¶
from datetime import datetime, timedelta
class SSLCertificateValidator(Validator):
"""Validate SSL certificate is not expired."""
def __init__(self, cert_path: str, warn_days: int = 30):
self.cert_path = cert_path
self.warn_days = warn_days
def validate(self, context: ValidationContext) -> None:
from pathlib import Path
import ssl
import OpenSSL
cert_file = Path(self.cert_path)
if not cert_file.exists():
context.add_check(
"SSL Certificate",
success=False,
message=f"File not found: {self.cert_path}"
)
return
# Load certificate
with open(cert_file, 'rb') as f:
cert_data = f.read()
cert = OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM,
cert_data
)
# Check expiration
not_after = datetime.strptime(
cert.get_notAfter().decode('ascii'),
'%Y%m%d%H%M%SZ'
)
days_until_expiry = (not_after - datetime.utcnow()).days
if days_until_expiry < 0:
context.add_check(
"SSL Certificate",
success=False,
message=f"Expired {abs(days_until_expiry)} days ago"
)
elif days_until_expiry < self.warn_days:
context.add_check(
"SSL Certificate",
success=True,
message=f"⚠️ Expires in {days_until_expiry} days (warning threshold: {self.warn_days})"
)
else:
context.add_check(
"SSL Certificate",
success=True,
message=f"Valid for {days_until_expiry} days"
)
5.3 Async Validator¶
import asyncio
class AsyncDatabaseValidator(Validator):
"""Async database connectivity check."""
def __init__(self, url: str):
self.url = url
def validate(self, context: ValidationContext) -> None:
# Run async code in sync context
result = asyncio.run(self._async_check())
context.add_check(
"Database (Async)",
success=result["success"],
message=result["message"]
)
async def _async_check(self) -> dict:
try:
import asyncpg
conn = await asyncpg.connect(self.url, timeout=5.0)
await conn.close()
return {
"success": True,
"message": "Connected successfully"
}
except Exception as e:
return {
"success": False,
"message": f"Connection failed: {e}"
}
6. Bootstrap Context¶
6.1 Accessing Context¶
from structum_lab.plugins.bootstrap import ValidationContext
context = ValidationContext()
# Add checks
context.add_check("Service A", True, "OK")
context.add_check("Service B", False, "Failed to connect")
# Query status
print(f"Total checks: {context.total_checks}")
print(f"Successful: {context.successful_checks}")
print(f"Failed: {context.failed_checks}")
# Get detailed report
print(context.get_report())
6.2 Report Format¶
┌─────────────────────────────────────┐
│ Bootstrap Validation Report │
├─────────────────────────────────────┤
│ Status: ❌ FAILED (5/7 checks passed)│
└─────────────────────────────────────┘
Environment Variables:
✓ DATABASE_URL: Present
✓ API_KEY: Present
✗ REDIS_URL: Missing
Python Version:
✓ Version 3.11.0 >= 3.9 (required)
Directories:
✓ /var/log/myapp: Exists and writable
✗ /etc/ssl/certs/app.pem: Not readable
6.3 Programmatic Access¶
# Check specific results
for check in context.checks:
print(f"{check.name}: {check.success}")
if not check.success:
print(f" Error: {check.message}")
# Export to JSON
import json
report_json = {
"total": context.total_checks,
"passed": context.successful_checks,
"failed": context.failed_checks,
"checks": [
{
"name": check.name,
"success": check.success,
"message": check.message
}
for check in context.checks
]
}
print(json.dumps(report_json, indent=2))
7. Error Handling¶
7.1 Exit on Failure (Production)¶
# Production mode: exit immediately on failure
boot = SystemBootstrapper()
boot.add_validator(EnvValidator(required=["DB_URL"]))
boot.run_or_exit() # sys.exit(1) if validation fails
# This line only executes if validation passed
start_application()
7.2 Continue on Failure (Development)¶
# Development mode: collect all errors
boot = SystemBootstrapper()
boot.add_validator(EnvValidator(required=["DB_URL"]))
boot.add_validator(DirectoryValidator("/var/log"))
result = boot.run()
if not result.success:
print("⚠️ Found validation issues:")
print(result.report)
# Developer can decide whether to continue
if input("Continue anyway? (y/n): ").lower() != 'y':
sys.exit(1)
start_application()
7.3 Custom Error Handling¶
class CustomBootstrapper(SystemBootstrapper):
"""Custom bootstrapper with logging."""
def on_validation_start(self):
"""Called before validation starts."""
logger.info("Starting bootstrap validation")
def on_validation_complete(self, context: ValidationContext):
"""Called after all validators run."""
logger.info(
f"Validation complete: {context.successful_checks}/{context.total_checks} passed"
)
if context.has_failures():
logger.error(f"Validation failed:\n{context.get_report()}")
# Send to monitoring
metrics.increment("bootstrap.failures")
def on_validator_error(self, validator: Validator, error: Exception):
"""Called if validator raises exception."""
logger.exception(f"Validator {validator.__class__.__name__} crashed: {error}")
8. Advanced Patterns¶
8.1 Conditional Validation¶
import os
boot = SystemBootstrapper()
# Always validate
boot.add_validator(PythonVersionValidator(min_version="3.9"))
# Production-only validators
if os.getenv("ENV") == "production":
boot.add_validator(SSLCertificateValidator("/etc/ssl/app.pem"))
boot.add_validator(DirectoryValidator("/var/log", writable=True))
# Development-only validators
if os.getenv("ENV") == "development":
boot.add_validator(PortAvailableValidator(8000))
boot.run_or_exit()
8.2 Validator Groups¶
class InfrastructureValidators:
"""Group of infrastructure validators."""
@staticmethod
def get_all() -> list[Validator]:
return [
EnvValidator(required=["DB_URL", "REDIS_URL"]),
DatabaseValidator.from_config(),
PortAvailableValidator(5432), # PostgreSQL
PortAvailableValidator(6379), # Redis
]
class SecurityValidators:
"""Group of security validators."""
@staticmethod
def get_all() -> list[Validator]:
return [
SSLCertificateValidator("/etc/ssl/app.pem"),
FileValidator("/etc/ssl/private/app.key", readable=True),
EnvValidator(required=["JWT_SECRET", "ENCRYPTION_KEY"]),
]
# Usage
boot = SystemBootstrapper()
for validator in InfrastructureValidators.get_all():
boot.add_validator(validator)
for validator in SecurityValidators.get_all():
boot.add_validator(validator)
boot.run_or_exit()
8.3 Progressive Validation¶
def bootstrap_application():
"""Multi-stage bootstrap with fail-fast."""
# Stage 1: Critical checks (fail immediately)
stage1 = SystemBootstrapper()
stage1.add_validator(PythonVersionValidator(min_version="3.9"))
stage1.add_validator(EnvValidator(required=["DATABASE_URL"]))
stage1.run_or_exit()
print("✓ Stage 1: Critical checks passed")
# Stage 2: Infrastructure (can warn)
stage2 = SystemBootstrapper()
stage2.add_validator(DatabaseValidator.from_config())
stage2.add_validator(DirectoryValidator("/var/log", create_if_missing=True))
result = stage2.run()
if not result.success:
print(f"⚠️ Stage 2: Infrastructure warnings:\n{result.report}")
print("✓ Stage 2: Infrastructure checks completed")
# Stage 3: Optional features
stage3 = SystemBootstrapper()
stage3.add_validator(EnvValidator(optional=["REDIS_URL", "CACHE_TTL"]))
stage3.run() # Never fails
print("✓ Stage 3: Optional features validated")
print("✅ Bootstrap complete - starting application")
9. Production Deployment¶
9.1 Container Health Checks¶
# health_check.py
from structum_lab.plugins.bootstrap import SystemBootstrapper, DatabaseValidator
def health_check() -> bool:
"""Health check for Kubernetes/Docker."""
boot = SystemBootstrapper()
boot.add_validator(DatabaseValidator.from_config())
result = boot.run()
return result.success
if __name__ == "__main__":
import sys
sys.exit(0 if health_check() else 1)
Dockerfile:
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD python health_check.py || exit 1
9.2 Systemd Integration¶
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target postgresql.service
[Service]
Type=simple
ExecStartPre=/usr/bin/python3 /app/bootstrap.py
ExecStart=/usr/bin/python3 /app/server.py
Restart=always
[Install]
WantedBy=multi-user.target
10. API Reference¶
See the Core Docs for details on protocols and base classes.