Files
gh-greyhaven-ai-claude-code…/skills/deployment-cloudflare/scripts/deploy.py
2025-11-29 18:29:12 +08:00

253 lines
6.3 KiB
Python
Executable File

#!/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()