# Hybrid Infrastructure as Code Approaches

## The Reality of Modern Infrastructure

Real-world infrastructure rarely uses a single tool. Modern projects combine:

- **Pulumi/Terraform** for core cloud resources
- **Platform CLIs** (Vercel, Supabase, Cloudflare) for specialized services
- **Python scripts** for custom automation
- **justfile/Makefile** for orchestration

This hybrid approach leverages best-in-class tools while maintaining a unified developer experience.

## The Practical Stack

```
┌─────────────────────────────────────────────────────────────────────┐
│                         justfile / Makefile                          │
│                    (Human interface, orchestration)                  │
└─────────────────────────────────────────────────────────────────────┘
                                    │
         ┌──────────────────────────┼──────────────────────────┐
         │                          │                          │
         ▼                          ▼                          ▼
┌─────────────────┐     ┌─────────────────────┐    ┌─────────────────┐
│     Pulumi      │     │   Platform CLIs      │    │ Python Scripts  │
│  (Core IaC)     │     │(vercel, supabase,   │    │  (Automation)   │
│                 │     │ wrangler, etc.)     │    │                 │
└─────────────────┘     └─────────────────────┘    └─────────────────┘
         │                          │                          │
         └──────────────────────────┼──────────────────────────┘
                                    │
                                    ▼
                    ┌───────────────────────────────┐
                    │        Cloud Resources        │
                    │  (AWS, Cloudflare, Vercel,   │
                    │   Supabase, etc.)            │
                    └───────────────────────────────┘
```

## Justfile as Orchestration Layer

### Why Justfile

- Simpler syntax than Makefile
- Cross-platform (Windows, macOS, Linux)
- Built-in argument handling
- Recipe dependencies
- Environment file support

### Example Justfile

```just
# justfile - Infrastructure orchestration

# Load environment variables
set dotenv-load

# Default recipe
default:
    @just --list

# ==========================================
# Development Setup
# ==========================================

# Set up local development environment
setup:
    python3 -m venv .venv
    source .venv/bin/activate && pip install -e ".[dev]"
    just db-setup
    @echo "✅ Development environment ready"

# ==========================================
# Database Operations
# ==========================================

# Set up local database
db-setup:
    supabase start
    supabase db reset

# Run database migrations
db-migrate env="dev":
    supabase db push --project-ref $(pulumi config get supabase:projectRef -s {{env}})

# Generate database types (for TypeScript projects)
db-types:
    supabase gen types typescript --local > src/types/database.ts

# ==========================================
# Infrastructure Deployment
# ==========================================

# Preview infrastructure changes
infra-preview env="dev":
    pulumi preview --stack {{env}}

# Deploy infrastructure
infra-deploy env="dev": (infra-preview env)
    pulumi up --yes --stack {{env}}

# Destroy infrastructure (careful!)
infra-destroy env="dev":
    @echo "⚠️  This will destroy all resources in {{env}}"
    @read -p "Type 'destroy' to confirm: " confirm && [ "$$confirm" = "destroy" ]
    pulumi destroy --yes --stack {{env}}

# ==========================================
# Application Deployment
# ==========================================

# Deploy to Vercel
deploy-web env="preview":
    vercel {{if env == "production" { "--prod" } else { "" }}}

# Deploy Cloudflare Workers
deploy-workers env="staging":
    wrangler deploy --env {{env}}

# Deploy Supabase Edge Functions
deploy-functions:
    supabase functions deploy --all

# Full deployment (all components)
deploy env="staging":
    @echo "🚀 Deploying to {{env}}..."
    just infra-deploy {{env}}
    just db-migrate {{env}}
    just deploy-web {{env}}
    just deploy-workers {{env}}
    just deploy-functions
    @echo "✅ Deployment complete"

# ==========================================
# Cost Management
# ==========================================

# Estimate infrastructure costs
cost-estimate:
    infracost breakdown --path infrastructure/

# Compare costs with main branch
cost-diff:
    infracost diff --path infrastructure/ --compare-to infracost-base.json

# ==========================================
# Utilities
# ==========================================

# Check all service statuses
status:
    @echo "📊 Infrastructure Status"
    @echo "========================"
    pulumi stack --stack dev
    @echo ""
    supabase status
    @echo ""
    vercel ls

# Tail logs from all services
logs service="all":
    {{if service == "all" || service == "vercel"}}
    vercel logs --follow &
    {{endif}}
    {{if service == "all" || service == "workers"}}
    wrangler tail &
    {{endif}}
    {{if service == "all" || service == "supabase"}}
    supabase functions logs --follow
    {{endif}}
```

## Combining Platform CLIs

### Vercel CLI

```bash
# Install
npm i -g vercel

# Login
vercel login

# Deploy (auto-detects framework)
vercel

# Deploy to production
vercel --prod

# Set environment variables
vercel env add DATABASE_URL production
vercel env add DATABASE_URL preview

# Pull environment to local
vercel env pull .env.local

# List deployments
vercel ls
```

### Supabase CLI

```bash
# Install
brew install supabase/tap/supabase

# Login
supabase login

# Link to project
supabase link --project-ref <project-ref>

# Start local development
supabase start

# Push migrations
supabase db push

# Deploy Edge Functions
supabase functions deploy <function-name>

# Generate types
supabase gen types typescript --local
```

### Cloudflare Wrangler

```bash
# Install
npm install -g wrangler

# Login
wrangler login

# Create new Worker
wrangler init my-worker

# Develop locally
wrangler dev

# Deploy
wrangler deploy

# Deploy to specific environment
wrangler deploy --env staging

# Set secrets
wrangler secret put API_KEY
```

## Python Automation Scripts

### Infrastructure Automation

```python
#!/usr/bin/env python3
"""Infrastructure automation scripts."""

import subprocess
import sys
from pathlib import Path


def run(cmd: str, check: bool = True) -> subprocess.CompletedProcess:
    """Run shell command."""
    print(f"+ {cmd}")
    return subprocess.run(cmd, shell=True, check=check)


def deploy_full_stack(env: str = "staging"):
    """Deploy complete infrastructure stack.

    Args:
        env: Target environment (dev, staging, production)
    """
    print(f"🚀 Deploying to {env}...")

    # 1. Deploy infrastructure with Pulumi
    print("\n📦 Deploying infrastructure...")
    run(f"pulumi up --yes --stack {env}")

    # 2. Run database migrations
    print("\n🗄️ Running migrations...")
    project_ref = run(
        f"pulumi config get supabase:projectRef -s {env}",
        check=True
    ).stdout.strip()
    run(f"supabase db push --project-ref {project_ref}")

    # 3. Deploy web application
    print("\n🌐 Deploying web app...")
    if env == "production":
        run("vercel --prod")
    else:
        run("vercel")

    # 4. Deploy Workers
    print("\n⚡ Deploying Workers...")
    run(f"wrangler deploy --env {env}")

    # 5. Deploy Edge Functions
    print("\n🔧 Deploying Edge Functions...")
    run("supabase functions deploy --all")

    print(f"\n✅ Deployment to {env} complete!")


def rollback(env: str, component: str):
    """Rollback specific component.

    Args:
        env: Target environment
        component: Component to rollback (infra, web, workers, functions)
    """
    print(f"⏪ Rolling back {component} in {env}...")

    if component == "infra":
        # Rollback to previous Pulumi state
        run(f"pulumi cancel --stack {env}")
        run(f"pulumi refresh --yes --stack {env}")

    elif component == "web":
        # Rollback Vercel deployment
        deployments = run("vercel ls --json", check=True)
        # Parse and rollback to previous deployment
        run(f"vercel rollback")

    elif component == "db":
        print("⚠️ Database rollbacks require manual intervention")
        print("Options:")
        print("  1. Create reverting migration")
        print("  2. Restore from backup")
        sys.exit(1)


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description="Infrastructure automation")
    subparsers = parser.add_subparsers(dest="command")

    # Deploy command
    deploy_parser = subparsers.add_parser("deploy")
    deploy_parser.add_argument("--env", default="staging")

    # Rollback command
    rollback_parser = subparsers.add_parser("rollback")
    rollback_parser.add_argument("--env", required=True)
    rollback_parser.add_argument("--component", required=True)

    args = parser.parse_args()

    if args.command == "deploy":
        deploy_full_stack(args.env)
    elif args.command == "rollback":
        rollback(args.env, args.component)
```

### Health Check Script

```python
#!/usr/bin/env python3
"""Health checks for all services."""

import requests
import subprocess
import sys


def check_web(url: str) -> bool:
    """Check web application health."""
    try:
        response = requests.get(f"{url}/api/health", timeout=10)
        return response.status_code == 200
    except Exception as e:
        print(f"  ❌ Web: {e}")
        return False


def check_database(connection_string: str) -> bool:
    """Check database connectivity."""
    try:
        import psycopg2
        conn = psycopg2.connect(connection_string)
        conn.close()
        return True
    except Exception as e:
        print(f"  ❌ Database: {e}")
        return False


def check_workers(worker_url: str) -> bool:
    """Check Cloudflare Workers."""
    try:
        response = requests.get(f"{worker_url}/health", timeout=10)
        return response.status_code == 200
    except Exception as e:
        print(f"  ❌ Workers: {e}")
        return False


def main():
    """Run all health checks."""
    import os

    checks = {
        "Web": lambda: check_web(os.getenv("APP_URL", "http://localhost:3000")),
        "Database": lambda: check_database(os.getenv("DATABASE_URL", "")),
        "Workers": lambda: check_workers(os.getenv("WORKER_URL", "")),
    }

    print("🏥 Running health checks...\n")

    all_healthy = True
    for name, check in checks.items():
        healthy = check()
        status = "✅" if healthy else "❌"
        print(f"  {status} {name}")
        all_healthy = all_healthy and healthy

    print()
    if all_healthy:
        print("✅ All services healthy")
        sys.exit(0)
    else:
        print("❌ Some services unhealthy")
        sys.exit(1)


if __name__ == "__main__":
    main()
```

## Integration Patterns

### Pulumi + Platform CLI Hybrid

```python
"""Pulumi with platform CLI integration."""

import pulumi
import subprocess


# Use Pulumi for core infrastructure
vpc = aws.ec2.Vpc(...)
database = aws.rds.Instance(...)

# Export values for platform CLIs
pulumi.export("database_url", database.endpoint.apply(
    lambda e: f"postgresql://user:pass@{e}/mydb"
))

# Use local command for Vercel (when Pulumi provider not available)
class VercelDeployment(pulumi.ComponentResource):
    def __init__(self, name: str, project_dir: str, **kwargs):
        super().__init__("custom:vercel:Deployment", name, **kwargs)

        # Deploy using Vercel CLI
        deployment = pulumi.local.Command(
            f"{name}-deploy",
            create=f"cd {project_dir} && vercel --yes",
            environment={"VERCEL_TOKEN": pulumi.Config().require_secret("vercelToken")},
            opts=pulumi.ResourceOptions(parent=self),
        )

        self.url = deployment.stdout

        self.register_outputs({"url": self.url})
```

### Shared Configuration

```python
"""Shared configuration across tools."""

# config/shared.py
import os
from dataclasses import dataclass


@dataclass
class SharedConfig:
    """Configuration shared across all tools."""

    environment: str
    project_name: str
    region: str

    # Service URLs (populated after deployment)
    web_url: str = ""
    api_url: str = ""
    database_url: str = ""

    @classmethod
    def from_environment(cls) -> "SharedConfig":
        return cls(
            environment=os.getenv("ENVIRONMENT", "development"),
            project_name=os.getenv("PROJECT_NAME", "myapp"),
            region=os.getenv("AWS_REGION", "us-east-1"),
            web_url=os.getenv("WEB_URL", ""),
            api_url=os.getenv("API_URL", ""),
            database_url=os.getenv("DATABASE_URL", ""),
        )


# Use in Pulumi
config = SharedConfig.from_environment()
pulumi.export("config", {
    "environment": config.environment,
    "project_name": config.project_name,
})

# Use in scripts
config = SharedConfig.from_environment()
subprocess.run(f"vercel --name {config.project_name}-{config.environment}")
```

## Best Practices

### 1. Single Source of Truth

Keep environment configuration in one place:

```bash
# .envrc - loaded by direnv
export ENVIRONMENT=development
export PROJECT_NAME=myapp
export AWS_REGION=us-east-1

# All tools read from environment
# Pulumi: pulumi.Config() or os.getenv()
# Vercel: automatic from environment
# Scripts: os.getenv()
```

### 2. Consistent Naming

```python
# Naming convention across all tools
def resource_name(base: str, env: str = None) -> str:
    """Generate consistent resource name."""
    env = env or os.getenv("ENVIRONMENT", "dev")
    project = os.getenv("PROJECT_NAME", "myapp")
    return f"{project}-{base}-{env}"

# Usage
bucket = aws.s3.Bucket(resource_name("uploads"))  # myapp-uploads-dev
```

### 3. Dependency Order

```just
# justfile - enforce dependency order

# Infrastructure must exist before app deployment
deploy-app env: (infra-deploy env)
    vercel --prod

# Database must be ready before migrations
db-migrate env: (infra-deploy env)
    supabase db push
```

### 4. Idempotent Operations

All operations should be safe to run multiple times:

```python
def deploy_infrastructure(env: str):
    """Deploy infrastructure (idempotent)."""
    # Pulumi handles idempotency
    run(f"pulumi up --yes --stack {env}")

    # Migrations are idempotent (track applied migrations)
    run("supabase db push")

    # Vercel deployments are additive
    run("vercel --prod")
```

## Related Documentation

- **Pulumi Guide**: [pulumi-guide.md](./pulumi-guide.md) - Core IaC
- **Vercel**: [vercel.md](./vercel.md) - Vercel CLI usage
- **Supabase**: [supabase.md](./supabase.md) - Supabase CLI usage
- **Cloudflare**: [cloudflare.md](./cloudflare.md) - Wrangler CLI usage
- **IaC Overview**: [infrastructure-as-code-overview.md](./infrastructure-as-code-overview.md) - Complete picture

---

**Key Takeaway**: Hybrid IaC combines best-in-class tools into a unified workflow. Use justfile as the orchestration layer, Pulumi for core infrastructure, platform CLIs for specialized services, and Python scripts for custom automation. The key is consistent configuration and clear dependency ordering.
