Initial commit
This commit is contained in:
252
skills/deployment-cloudflare/scripts/deploy.py
Executable file
252
skills/deployment-cloudflare/scripts/deploy.py
Executable file
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Deploy Grey Haven applications to Cloudflare Workers with Doppler secrets.
|
||||
|
||||
This script automates the deployment workflow:
|
||||
1. Runs tests with Doppler environment
|
||||
2. Builds the application
|
||||
3. Runs database migrations
|
||||
4. Deploys to Cloudflare Workers with Wrangler
|
||||
5. Injects secrets from Doppler
|
||||
6. Runs smoke tests
|
||||
7. Automatically rolls back on failure
|
||||
|
||||
Usage:
|
||||
# Development deployment
|
||||
python scripts/deploy.py --env dev
|
||||
|
||||
# Staging deployment
|
||||
python scripts/deploy.py --env staging
|
||||
|
||||
# Production deployment (with confirmation)
|
||||
python scripts/deploy.py --env production
|
||||
|
||||
# Skip tests (not recommended)
|
||||
python scripts/deploy.py --env staging --skip-tests
|
||||
|
||||
# Dry run (show what would happen)
|
||||
python scripts/deploy.py --env production --dry-run
|
||||
|
||||
Always run with --help first to see all options.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def run_command(cmd: str, description: str, dry_run: bool = False) -> bool:
|
||||
"""Run a shell command and return success status."""
|
||||
print(f"\n{'[DRY RUN] ' if dry_run else ''}→ {description}")
|
||||
print(f" Command: {cmd}")
|
||||
|
||||
if dry_run:
|
||||
return True
|
||||
|
||||
result = subprocess.run(cmd, shell=True, capture_output=False)
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def confirm_production_deploy() -> bool:
|
||||
"""Ask for confirmation before production deployment."""
|
||||
print("\nWARNING: WARNING: You are about to deploy to PRODUCTION")
|
||||
print("This will affect live users.")
|
||||
response = input("Type 'deploy to production' to confirm: ")
|
||||
return response.strip().lower() == "deploy to production"
|
||||
|
||||
|
||||
def get_wrangler_config(env: str) -> str:
|
||||
"""Get the appropriate wrangler config file for environment."""
|
||||
if env == "production":
|
||||
return "wrangler.production.toml"
|
||||
elif env == "staging":
|
||||
return "wrangler.staging.toml"
|
||||
else:
|
||||
return "wrangler.toml"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Deploy Grey Haven application to Cloudflare Workers",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
python scripts/deploy.py --env dev
|
||||
python scripts/deploy.py --env staging --skip-migrations
|
||||
python scripts/deploy.py --env production --dry-run
|
||||
|
||||
Environments:
|
||||
dev - Development (wrangler.toml)
|
||||
staging - Staging (wrangler.staging.toml)
|
||||
production - Production (wrangler.production.toml)
|
||||
|
||||
Doppler Configuration:
|
||||
Requires DOPPLER_TOKEN environment variable or doppler CLI configured.
|
||||
Secrets are injected from Doppler config matching the environment.
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--env",
|
||||
required=True,
|
||||
choices=["dev", "staging", "production"],
|
||||
help="Deployment environment"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-tests",
|
||||
action="store_true",
|
||||
help="Skip running tests (not recommended)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-migrations",
|
||||
action="store_true",
|
||||
help="Skip database migrations"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-smoke-tests",
|
||||
action="store_true",
|
||||
help="Skip smoke tests after deployment"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would happen without executing"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Production confirmation
|
||||
if args.env == "production" and not args.dry_run:
|
||||
if not confirm_production_deploy():
|
||||
print("\nERROR: Production deployment cancelled")
|
||||
sys.exit(1)
|
||||
|
||||
env = args.env
|
||||
wrangler_config = get_wrangler_config(env)
|
||||
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f" Grey Haven Deployment to {env.upper()}")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
# Step 1: Run tests
|
||||
if not args.skip_tests:
|
||||
success = run_command(
|
||||
f"doppler run --config test -- npm run test",
|
||||
"Running tests",
|
||||
args.dry_run
|
||||
)
|
||||
if not success:
|
||||
print("\nERROR: Tests failed. Deployment aborted.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\nWARNING: Skipping tests (--skip-tests)")
|
||||
|
||||
# Step 2: Build application
|
||||
success = run_command(
|
||||
f"doppler run --config {env} -- npm run build",
|
||||
f"Building application for {env}",
|
||||
args.dry_run
|
||||
)
|
||||
if not success:
|
||||
print("\nERROR: Build failed. Deployment aborted.")
|
||||
sys.exit(1)
|
||||
|
||||
# Step 3: Run database migrations
|
||||
if not args.skip_migrations:
|
||||
success = run_command(
|
||||
f"doppler run --config {env} -- npm run db:migrate",
|
||||
"Running database migrations",
|
||||
args.dry_run
|
||||
)
|
||||
if not success:
|
||||
print("\nERROR: Migrations failed. Deployment aborted.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\nWARNING: Skipping migrations (--skip-migrations)")
|
||||
|
||||
# Step 4: Deploy to Cloudflare Workers
|
||||
success = run_command(
|
||||
f"npx wrangler deploy --config {wrangler_config}",
|
||||
f"Deploying to Cloudflare Workers ({wrangler_config})",
|
||||
args.dry_run
|
||||
)
|
||||
if not success:
|
||||
print("\nERROR: Deployment failed.")
|
||||
sys.exit(1)
|
||||
|
||||
# Step 5: Inject Doppler secrets to Cloudflare Workers
|
||||
if not args.dry_run:
|
||||
print("\n→ Injecting Doppler secrets to Cloudflare Workers")
|
||||
print(" This may take a minute...")
|
||||
|
||||
# Download secrets from Doppler
|
||||
result = subprocess.run(
|
||||
f"doppler secrets download --config {env} --format json",
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
print("\nERROR: Failed to download Doppler secrets")
|
||||
sys.exit(1)
|
||||
|
||||
secrets = json.loads(result.stdout)
|
||||
|
||||
# Inject each secret to Cloudflare Workers
|
||||
for key, value in secrets.items():
|
||||
# Skip non-secret env vars (like NODE_ENV, ENVIRONMENT)
|
||||
if key in ["NODE_ENV", "ENVIRONMENT", "CI"]:
|
||||
continue
|
||||
|
||||
cmd = f'echo "{value}" | npx wrangler secret put {key} --config {wrangler_config}'
|
||||
subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
print(f" ✓ Injected {len(secrets)} secrets")
|
||||
else:
|
||||
print("\n[DRY RUN] → Would inject Doppler secrets to Cloudflare Workers")
|
||||
|
||||
# Step 6: Run smoke tests
|
||||
if not args.skip_smoke_tests:
|
||||
success = run_command(
|
||||
f"doppler run --config {env} -- npm run test:e2e:smoke",
|
||||
"Running smoke tests",
|
||||
args.dry_run
|
||||
)
|
||||
if not success:
|
||||
print("\nWARNING: Smoke tests failed. Rolling back deployment...")
|
||||
|
||||
if not args.dry_run:
|
||||
subprocess.run(
|
||||
f"npx wrangler rollback --config {wrangler_config}",
|
||||
shell=True
|
||||
)
|
||||
|
||||
print("\nERROR: Deployment rolled back due to smoke test failure")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\nWARNING: Skipping smoke tests (--skip-smoke-tests)")
|
||||
|
||||
# Success!
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f" SUCCESS: Deployment to {env.upper()} successful!")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
if env == "production":
|
||||
print(f"\n Production URL: https://app.greyhaven.studio")
|
||||
elif env == "staging":
|
||||
print(f"\n Staging URL: https://staging.greyhaven.studio")
|
||||
else:
|
||||
print(f"\n Dev URL: https://dev.greyhaven.studio")
|
||||
|
||||
print("\nNext steps:")
|
||||
print(" • Monitor logs: npx wrangler tail")
|
||||
print(" • Check Sentry for errors")
|
||||
print(" • Verify Axiom logs")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
198
skills/deployment-cloudflare/scripts/migrate.py
Executable file
198
skills/deployment-cloudflare/scripts/migrate.py
Executable file
@@ -0,0 +1,198 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Run database migrations for Grey Haven applications with Doppler.
|
||||
|
||||
Supports both Drizzle (TypeScript) and Alembic (Python) migrations
|
||||
across multiple environments.
|
||||
|
||||
Usage:
|
||||
# Run migrations for development
|
||||
python scripts/migrate.py --env dev
|
||||
|
||||
# Run migrations for staging
|
||||
python scripts/migrate.py --env staging
|
||||
|
||||
# Run migrations for production (with confirmation)
|
||||
python scripts/migrate.py --env production
|
||||
|
||||
# Rollback last migration
|
||||
python scripts/migrate.py --env dev --rollback
|
||||
|
||||
# Rollback to specific migration
|
||||
python scripts/migrate.py --env dev --rollback --to 20250115_add_users
|
||||
|
||||
# Dry run (show what would happen)
|
||||
python scripts/migrate.py --env production --dry-run
|
||||
|
||||
# Use Alembic instead of Drizzle
|
||||
python scripts/migrate.py --env dev --backend alembic
|
||||
|
||||
Always run with --help first to see all options.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def run_command(cmd: str, description: str, dry_run: bool = False) -> bool:
|
||||
"""Run a shell command and return success status."""
|
||||
print(f"\n{'[DRY RUN] ' if dry_run else ''}→ {description}")
|
||||
print(f" Command: {cmd}")
|
||||
|
||||
if dry_run:
|
||||
return True
|
||||
|
||||
result = subprocess.run(cmd, shell=True, capture_output=False)
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def confirm_production_migration() -> bool:
|
||||
"""Ask for confirmation before production migration."""
|
||||
print("\nWARNING: WARNING: You are about to run migrations on PRODUCTION database")
|
||||
print("This operation is IRREVERSIBLE and will affect live data.")
|
||||
response = input("Type 'run production migrations' to confirm: ")
|
||||
return response.strip().lower() == "run production migrations"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run database migrations with Doppler environment variables",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Run Drizzle migrations for development
|
||||
python scripts/migrate.py --env dev
|
||||
|
||||
# Rollback last Drizzle migration
|
||||
python scripts/migrate.py --env dev --rollback
|
||||
|
||||
# Run Alembic migrations for staging
|
||||
python scripts/migrate.py --env staging --backend alembic
|
||||
|
||||
# Rollback to specific Alembic migration
|
||||
python scripts/migrate.py --env staging --backend alembic --rollback --to abc123
|
||||
|
||||
Environments:
|
||||
dev - Local development database
|
||||
test - CI/CD test database
|
||||
staging - Staging database
|
||||
production - Production database (requires confirmation)
|
||||
|
||||
Backends:
|
||||
drizzle - Drizzle Kit (TypeScript/TanStack Start)
|
||||
alembic - Alembic (Python/FastAPI)
|
||||
|
||||
Doppler Configuration:
|
||||
Requires doppler CLI configured with appropriate access.
|
||||
Uses DATABASE_URL_ADMIN from Doppler config.
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--env",
|
||||
required=True,
|
||||
choices=["dev", "test", "staging", "production"],
|
||||
help="Environment to run migrations against"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--backend",
|
||||
default="drizzle",
|
||||
choices=["drizzle", "alembic"],
|
||||
help="Migration backend to use (default: drizzle)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rollback",
|
||||
action="store_true",
|
||||
help="Rollback migrations instead of applying"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--to",
|
||||
type=str,
|
||||
help="Rollback to specific migration (use with --rollback)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would happen without executing"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Production confirmation
|
||||
if args.env == "production" and not args.dry_run and not args.rollback:
|
||||
if not confirm_production_migration():
|
||||
print("\nERROR: Production migration cancelled")
|
||||
sys.exit(1)
|
||||
|
||||
env = args.env
|
||||
backend = args.backend
|
||||
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f" Database Migration - {env.upper()} ({backend.upper()})")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
# Check Doppler configuration
|
||||
if not args.dry_run:
|
||||
result = subprocess.run(
|
||||
f"doppler secrets get DATABASE_URL_ADMIN --config {env}",
|
||||
shell=True,
|
||||
capture_output=True
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"\nERROR: Failed to get DATABASE_URL_ADMIN from Doppler config '{env}'")
|
||||
print(" Make sure Doppler is configured: doppler setup")
|
||||
sys.exit(1)
|
||||
|
||||
# Construct migration command
|
||||
if backend == "drizzle":
|
||||
if args.rollback:
|
||||
if args.to:
|
||||
cmd = f"doppler run --config {env} -- drizzle-kit migrate:rollback --to {args.to}"
|
||||
else:
|
||||
cmd = f"doppler run --config {env} -- drizzle-kit migrate:rollback"
|
||||
description = "Rolling back Drizzle migration"
|
||||
else:
|
||||
cmd = f"doppler run --config {env} -- drizzle-kit push:pg"
|
||||
description = "Applying Drizzle migrations"
|
||||
|
||||
elif backend == "alembic":
|
||||
if args.rollback:
|
||||
if args.to:
|
||||
cmd = f"doppler run --config {env} -- alembic downgrade {args.to}"
|
||||
else:
|
||||
cmd = f"doppler run --config {env} -- alembic downgrade -1"
|
||||
description = "Rolling back Alembic migration"
|
||||
else:
|
||||
cmd = f"doppler run --config {env} -- alembic upgrade head"
|
||||
description = "Applying Alembic migrations"
|
||||
|
||||
# Run migration
|
||||
success = run_command(cmd, description, args.dry_run)
|
||||
|
||||
if not success:
|
||||
print(f"\nERROR: Migration failed for {env}")
|
||||
sys.exit(1)
|
||||
|
||||
# Success!
|
||||
print(f"\n{'=' * 70}")
|
||||
if args.rollback:
|
||||
print(f" SUCCESS: Rollback successful for {env.upper()}")
|
||||
else:
|
||||
print(f" SUCCESS: Migration successful for {env.upper()}")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
if not args.rollback:
|
||||
print("\nNext steps:")
|
||||
print(" • Verify schema changes in database")
|
||||
print(" • Run tests: doppler run --config test -- npm run test")
|
||||
print(" • Deploy application if migrations succeeded")
|
||||
else:
|
||||
print("\nNext steps:")
|
||||
print(" • Verify rollback was successful")
|
||||
print(" • Re-deploy previous application version if needed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
239
skills/deployment-cloudflare/scripts/rollback.py
Executable file
239
skills/deployment-cloudflare/scripts/rollback.py
Executable file
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Rollback Grey Haven Cloudflare Workers deployment to previous version.
|
||||
|
||||
This script handles emergency rollbacks when a deployment fails or causes
|
||||
production issues. It can rollback both the Workers deployment and database
|
||||
migrations.
|
||||
|
||||
Usage:
|
||||
# Rollback to previous Workers deployment
|
||||
python scripts/rollback.py --env production
|
||||
|
||||
# Rollback Workers and database migration
|
||||
python scripts/rollback.py --env production --with-migration
|
||||
|
||||
# Rollback to specific deployment ID
|
||||
python scripts/rollback.py --env production --deployment-id abc123
|
||||
|
||||
# Rollback database only
|
||||
python scripts/rollback.py --env production --migration-only
|
||||
|
||||
# Dry run (show what would happen)
|
||||
python scripts/rollback.py --env production --dry-run
|
||||
|
||||
Always run with --help first to see all options.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def run_command(cmd: str, description: str, dry_run: bool = False, capture: bool = False) -> tuple[bool, Optional[str]]:
|
||||
"""Run a shell command and return success status and output."""
|
||||
print(f"\n{'[DRY RUN] ' if dry_run else ''}→ {description}")
|
||||
print(f" Command: {cmd}")
|
||||
|
||||
if dry_run:
|
||||
return True, None
|
||||
|
||||
result = subprocess.run(cmd, shell=True, capture_output=capture, text=True)
|
||||
return result.returncode == 0, result.stdout if capture else None
|
||||
|
||||
|
||||
def confirm_production_rollback() -> bool:
|
||||
"""Ask for confirmation before production rollback."""
|
||||
print("\nWARNING: WARNING: You are about to ROLLBACK PRODUCTION deployment")
|
||||
print("This will affect live users immediately.")
|
||||
response = input("Type 'rollback production' to confirm: ")
|
||||
return response.strip().lower() == "rollback production"
|
||||
|
||||
|
||||
def get_wrangler_config(env: str) -> str:
|
||||
"""Get the appropriate wrangler config file for environment."""
|
||||
if env == "production":
|
||||
return "wrangler.production.toml"
|
||||
elif env == "staging":
|
||||
return "wrangler.staging.toml"
|
||||
else:
|
||||
return "wrangler.toml"
|
||||
|
||||
|
||||
def list_recent_deployments(wrangler_config: str, dry_run: bool = False) -> None:
|
||||
"""List recent deployments for reference."""
|
||||
if dry_run:
|
||||
print("\n[DRY RUN] → Would list recent deployments")
|
||||
return
|
||||
|
||||
print("\n→ Fetching recent deployments...")
|
||||
subprocess.run(
|
||||
f"npx wrangler deployments list --config {wrangler_config}",
|
||||
shell=True
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Rollback Grey Haven Cloudflare Workers deployment",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Emergency rollback of production Workers deployment
|
||||
python scripts/rollback.py --env production
|
||||
|
||||
# Rollback Workers and database migration
|
||||
python scripts/rollback.py --env production --with-migration
|
||||
|
||||
# Rollback to specific deployment
|
||||
python scripts/rollback.py --env production --deployment-id abc123
|
||||
|
||||
# Rollback database migration only
|
||||
python scripts/rollback.py --env production --migration-only --backend drizzle
|
||||
|
||||
Environments:
|
||||
dev - Development
|
||||
staging - Staging
|
||||
production - Production (requires confirmation)
|
||||
|
||||
Emergency Rollback Procedure:
|
||||
1. Identify the issue (check Sentry, Axiom, Cloudflare logs)
|
||||
2. Run rollback script with appropriate flags
|
||||
3. Verify rollback with smoke tests
|
||||
4. Notify team and update Linear issue
|
||||
5. Create postmortem for root cause analysis
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--env",
|
||||
required=True,
|
||||
choices=["dev", "staging", "production"],
|
||||
help="Environment to rollback"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--deployment-id",
|
||||
type=str,
|
||||
help="Specific deployment ID to rollback to (optional)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--with-migration",
|
||||
action="store_true",
|
||||
help="Also rollback database migration"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--migration-only",
|
||||
action="store_true",
|
||||
help="Rollback database migration only (not Workers)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--backend",
|
||||
default="drizzle",
|
||||
choices=["drizzle", "alembic"],
|
||||
help="Migration backend (default: drizzle)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would happen without executing"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Production confirmation
|
||||
if args.env == "production" and not args.dry_run:
|
||||
if not confirm_production_rollback():
|
||||
print("\nERROR: Production rollback cancelled")
|
||||
sys.exit(1)
|
||||
|
||||
env = args.env
|
||||
wrangler_config = get_wrangler_config(env)
|
||||
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f" Emergency Rollback - {env.upper()}")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
# Rollback Workers deployment (unless migration-only)
|
||||
if not args.migration_only:
|
||||
# List recent deployments first
|
||||
if not args.deployment_id:
|
||||
list_recent_deployments(wrangler_config, args.dry_run)
|
||||
|
||||
# Construct rollback command
|
||||
if args.deployment_id:
|
||||
cmd = f"npx wrangler rollback --deployment-id {args.deployment_id} --config {wrangler_config}"
|
||||
description = f"Rolling back to deployment {args.deployment_id}"
|
||||
else:
|
||||
cmd = f"npx wrangler rollback --config {wrangler_config}"
|
||||
description = "Rolling back to previous deployment"
|
||||
|
||||
success, _ = run_command(cmd, description, args.dry_run)
|
||||
|
||||
if not success:
|
||||
print(f"\nERROR: Workers rollback failed for {env}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\nSUCCESS: Workers deployment rolled back successfully")
|
||||
|
||||
# Rollback database migration (if requested)
|
||||
if args.with_migration or args.migration_only:
|
||||
backend = args.backend
|
||||
|
||||
print(f"\n→ Rolling back {backend} migration for {env}")
|
||||
|
||||
if backend == "drizzle":
|
||||
cmd = f"doppler run --config {env} -- drizzle-kit migrate:rollback"
|
||||
elif backend == "alembic":
|
||||
cmd = f"doppler run --config {env} -- alembic downgrade -1"
|
||||
|
||||
success, _ = run_command(cmd, f"Rolling back {backend} migration", args.dry_run)
|
||||
|
||||
if not success:
|
||||
print(f"\nERROR: Database migration rollback failed for {env}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\nSUCCESS: Database migration rolled back successfully")
|
||||
|
||||
# Success!
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f" SUCCESS: Rollback complete for {env.upper()}")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
# Run smoke tests
|
||||
print("\n→ Running smoke tests to verify rollback...")
|
||||
success, _ = run_command(
|
||||
f"doppler run --config {env} -- npm run test:e2e:smoke",
|
||||
"Verifying rollback with smoke tests",
|
||||
args.dry_run
|
||||
)
|
||||
|
||||
if not success:
|
||||
print("\nWARNING: Warning: Smoke tests failed after rollback")
|
||||
print(" Manual verification required!")
|
||||
else:
|
||||
print("\nSUCCESS: Smoke tests passed - rollback verified")
|
||||
|
||||
# Post-rollback checklist
|
||||
print("\n📋 Post-Rollback Checklist:")
|
||||
print(" ✓ Deployment rolled back")
|
||||
if args.with_migration or args.migration_only:
|
||||
print(" ✓ Database migration rolled back")
|
||||
print("\n WARNING: Action Items:")
|
||||
print(" • Check Sentry for errors")
|
||||
print(" • Verify Axiom logs")
|
||||
print(" • Monitor Cloudflare Workers analytics")
|
||||
print(" • Update Linear issue with rollback status")
|
||||
print(" • Create postmortem for root cause analysis")
|
||||
print(" • Fix the issue before re-deploying")
|
||||
|
||||
if env == "production":
|
||||
print(f"\n Production URL: https://app.greyhaven.studio")
|
||||
elif env == "staging":
|
||||
print(f"\n Staging URL: https://staging.greyhaven.studio")
|
||||
else:
|
||||
print(f"\n Dev URL: https://dev.greyhaven.studio")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user