diff --git a/docs/AZURE-INFRASTRUCTURE.md b/docs/AZURE-INFRASTRUCTURE.md new file mode 100644 index 0000000..d1379e9 --- /dev/null +++ b/docs/AZURE-INFRASTRUCTURE.md @@ -0,0 +1,593 @@ +# Azure Infrastructure Guide + +> **Document Version:** 1.0.0 +> **Last Updated:** 2025-12-21 +> **Status:** Active +> **TOGAF Phase:** Phase D (Technology Architecture) + +--- + +## Table of Contents + +1. [Overview](#overview) +2. [Architecture Diagram](#architecture-diagram) +3. [Resource Inventory](#resource-inventory) +4. [Prerequisites](#prerequisites) +5. [Deployment Guide](#deployment-guide) +6. [Environment Configuration](#environment-configuration) +7. [Security Configuration](#security-configuration) +8. [Cost Estimation](#cost-estimation) +9. [Monitoring and Observability](#monitoring-and-observability) +10. [Troubleshooting](#troubleshooting) +11. [Related Documents](#related-documents) + +--- + +## Overview + +Luminous uses Azure as its cloud platform, following the **TP-1: Azure-Native Stack** architecture principle. The infrastructure is defined as code using Bicep with Azure Verified Modules (AVMs) for consistency and best practices. + +### Key Design Decisions + +| Decision | Rationale | +|----------|-----------| +| **Bicep over ARM/Terraform** | Native Azure support, simpler syntax, better tooling | +| **Azure Verified Modules** | Pre-validated, secure, and compliant patterns | +| **Multi-tenant via partitioning** | Cost-effective, simpler operations | +| **Managed services** | Reduced operational overhead | + +### Infrastructure Principles + +Following TOGAF TP-4 (Infrastructure as Code): + +- **Reproducible**: Same code deploys identical infrastructure +- **Version controlled**: All changes tracked in Git +- **Environment parity**: Dev/staging/prod use same templates +- **Self-documenting**: Bicep templates serve as documentation + +--- + +## Architecture Diagram + +``` + ┌─────────────────────────────────────────┐ + │ AZURE SUBSCRIPTION │ + └─────────────────────────────────────────┘ + │ + ┌─────────────────────────────────────────┐ + │ RESOURCE GROUP: rg-lum-{env} │ + └─────────────────────────────────────────┘ + │ + ┌──────────────────────────────────────────────┼──────────────────────────────────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ CLIENTS │ │ COMPUTE │ │ DATA │ +├───────────────┤ ├───────────────┤ ├───────────────┤ +│ │ HTTPS │ │ │ │ +│ Static Web │◄────────────────────────────│ App Service │ │ Cosmos DB │ +│ App (SWA) │ │ (.NET API) │◄────────────────────────────│ (Core Data) │ +│ │ │ │ │ │ +│ stapp-lum-* │ │ app-lum-* │ │ cosmos-lum-* │ +└───────────────┘ └───────┬───────┘ └───────────────┘ + ▲ │ ▲ + │ │ SignalR │ + │ ┌───────┴───────┐ │ + │ │ │ │ + │ │ SignalR │ │ + │ │ Service │ │ + │ │ │ │ + │ │ sigr-lum-* │ │ + │ └───────────────┘ │ + │ │ + │ ┌───────────────┐ │ + │ │ │ │ + │ │ Function App │ │ + │ │ (Sync Jobs) │──────────────────────────────────────┤ + │ │ │ │ + │ │ func-lum-sync │ │ + │ └───────────────┘ │ + │ │ + │ ┌───────────────┐ ┌───────────────┐ + │ │ │ │ │ + │ │ Function App │ │ Blob Storage │ + │ │ (Import) │────────────────────────────▶│ (Media) │ + │ │ │ │ │ + │ │func-lum-import│ │ stlum* │ + │ └───────────────┘ └───────────────┘ + │ │ + │ │ Service Bus + │ ┌───────┴───────┐ + │ │ │ + │ │ Service Bus │ + │ │ (Messaging) │ + │ │ │ + │ │ sb-lum-* │ + │ └───────────────┘ + │ + │ ┌───────────────────────────────────────────────────────┐ + │ │ SECURITY │ + │ ├───────────────┬───────────────┬───────────────────────┤ + │ │ Key Vault │ App Config │ Redis Cache │ + │ │ (Secrets) │ (Settings) │ (Sessions) │ + │ │ kv-lum-* │ appcs-lum-* │ redis-lum-* │ + │ └───────────────┴───────────────┴───────────────────────┘ + │ + │ ┌───────────────────────────────────────────────────────┐ + │ │ MONITORING │ + │ ├───────────────────────────────┬───────────────────────┤ + │ │ Log Analytics │ Application Insights │ + │ │ log-lum-* │ appi-lum-* │ + │ └───────────────────────────────┴───────────────────────┘ + │ + │ +┌───────┴───────┐ ┌───────────────┐ ┌───────────────┐ +│ iOS App │ │ Android App │ │ Display App │ +│ (Swift) │ │ (Kotlin) │ │ (Electron) │ +└───────────────┘ └───────────────┘ └───────────────┘ +``` + +--- + +## Resource Inventory + +### Compute Resources + +| Resource | Name Pattern | Purpose | SKU (Dev/Prod) | +|----------|--------------|---------|----------------| +| App Service Plan | `asp-lum-{env}` | Hosts API and Functions | B1 / P1v3 | +| App Service | `app-lum-{env}-api` | .NET 10 REST API | - | +| Function App | `func-lum-{env}-sync` | Calendar sync jobs | - | +| Function App | `func-lum-{env}-import` | Import processing | - | +| Static Web App | `stapp-lum-{env}` | Angular web app | Free / Standard | + +### Data Resources + +| Resource | Name Pattern | Purpose | SKU (Dev/Prod) | +|----------|--------------|---------|----------------| +| Cosmos DB | `cosmos-lum-{env}` | Document database | Serverless / Provisioned | +| Storage Account | `stlum{env}` | Blob storage | Standard_LRS / Standard_GRS | +| Redis Cache | `redis-lum-{env}` | Session cache | Basic C0 / Standard C1 | + +### Messaging Resources + +| Resource | Name Pattern | Purpose | SKU (Dev/Prod) | +|----------|--------------|---------|----------------| +| Service Bus | `sb-lum-{env}` | Async messaging | Basic / Standard | +| SignalR Service | `sigr-lum-{env}` | Real-time sync | Free / Standard | + +### Security Resources + +| Resource | Name Pattern | Purpose | +|----------|--------------|---------| +| Key Vault | `kv-lum-{env}` | Secrets management | +| App Configuration | `appcs-lum-{env}` | Centralized config | + +### Monitoring Resources + +| Resource | Name Pattern | Purpose | +|----------|--------------|---------| +| Log Analytics | `log-lum-{env}` | Centralized logging | +| Application Insights | `appi-lum-{env}` | APM and diagnostics | + +--- + +## Prerequisites + +### Required Tools + +| Tool | Version | Purpose | +|------|---------|---------| +| Azure CLI | 2.50+ | Azure resource management | +| Bicep CLI | 0.22+ | Infrastructure as Code | +| Git | 2.30+ | Version control | + +### Installation + +```bash +# Install Azure CLI (macOS) +brew install azure-cli + +# Install Azure CLI (Windows) +winget install Microsoft.AzureCLI + +# Install Bicep CLI +az bicep install + +# Verify installations +az --version +az bicep version +``` + +### Azure Access Requirements + +1. **Azure Subscription** with Owner or Contributor role +2. **Service Principal** for CI/CD (optional) +3. **Registered Resource Providers**: + - Microsoft.DocumentDB + - Microsoft.Web + - Microsoft.Storage + - Microsoft.Cache + - Microsoft.SignalRService + - Microsoft.ServiceBus + - Microsoft.KeyVault + - Microsoft.AppConfiguration + - Microsoft.Insights + - Microsoft.OperationalInsights + +```bash +# Register required providers +az provider register --namespace Microsoft.DocumentDB +az provider register --namespace Microsoft.Web +az provider register --namespace Microsoft.Storage +az provider register --namespace Microsoft.Cache +az provider register --namespace Microsoft.SignalRService +az provider register --namespace Microsoft.ServiceBus +az provider register --namespace Microsoft.KeyVault +az provider register --namespace Microsoft.AppConfiguration +az provider register --namespace Microsoft.Insights +az provider register --namespace Microsoft.OperationalInsights +``` + +--- + +## Deployment Guide + +### Directory Structure + +``` +infra/ +├── bicep/ +│ ├── main.bicep # Main orchestration (uses AVMs from registry) +│ └── parameters/ # Environment configs +│ ├── dev.bicepparam +│ ├── staging.bicepparam +│ └── prod.bicepparam +└── scripts/ + ├── deploy.sh # Bash deployment script + └── deploy.ps1 # PowerShell deployment script +``` + +### Azure Verified Modules (AVMs) + +All resources are deployed using AVMs directly from the public Bicep registry (`br/public:avm/res/...`). This approach provides: + +- **Pre-validated patterns** - Security and compliance built-in +- **Maintained by Microsoft/community** - Regular updates and bug fixes +- **Semantic versioning** - Predictable upgrades +- **Less code to maintain** - No custom module implementations + +| Resource | AVM Reference | +|----------|---------------| +| Resource Group | `br/public:avm/res/resources/resource-group:0.4.0` | +| Log Analytics | `br/public:avm/res/operational-insights/workspace:0.9.0` | +| App Insights | `br/public:avm/res/insights/component:0.4.1` | +| Key Vault | `br/public:avm/res/key-vault/vault:0.9.0` | +| App Configuration | `br/public:avm/res/app-configuration/configuration-store:0.5.1` | +| Cosmos DB | `br/public:avm/res/document-db/database-account:0.8.1` | +| Storage Account | `br/public:avm/res/storage/storage-account:0.14.3` | +| Redis Cache | `br/public:avm/res/cache/redis:0.8.0` | +| Service Bus | `br/public:avm/res/service-bus/namespace:0.10.0` | +| SignalR | `br/public:avm/res/signal-r-service/signal-r:0.5.0` | +| App Service Plan | `br/public:avm/res/web/serverfarm:0.3.0` | +| App Service/Functions | `br/public:avm/res/web/site:0.11.1` | +| Static Web App | `br/public:avm/res/web/static-site:0.6.0` | + +### Quick Start + +```bash +# 1. Login to Azure +az login + +# 2. Select subscription +az account set --subscription "" + +# 3. Deploy to development +cd infra/scripts +./deploy.sh dev + +# 4. Deploy to staging +./deploy.sh staging + +# 5. Deploy to production (with what-if preview first) +./deploy.sh prod --what-if +./deploy.sh prod +``` + +### Detailed Deployment Steps + +#### Step 1: Authenticate + +```bash +# Interactive login +az login + +# Or service principal login (CI/CD) +az login --service-principal \ + --username $ARM_CLIENT_ID \ + --password $ARM_CLIENT_SECRET \ + --tenant $ARM_TENANT_ID +``` + +#### Step 2: Validate Templates + +```bash +# Build and validate Bicep +az bicep build --file infra/bicep/main.bicep + +# What-if analysis +az deployment sub what-if \ + --location eastus2 \ + --template-file infra/bicep/main.bicep \ + --parameters @infra/bicep/parameters/dev.bicepparam +``` + +#### Step 3: Deploy + +```bash +# Deploy infrastructure +az deployment sub create \ + --name "luminous-$(date +%Y%m%d)" \ + --location eastus2 \ + --template-file infra/bicep/main.bicep \ + --parameters @infra/bicep/parameters/dev.bicepparam +``` + +#### Step 4: Verify + +```bash +# List deployment outputs +az deployment sub show \ + --name "luminous-$(date +%Y%m%d)" \ + --query properties.outputs + +# Test API endpoint +curl https://app-lum-dev-api.azurewebsites.net/health +``` + +--- + +## Environment Configuration + +### Development + +- **Purpose**: Local development and testing +- **Cost optimization**: Serverless Cosmos DB, Basic SKUs +- **Features**: Full feature set, relaxed security + +``` +Cosmos DB: Serverless +App Service: B1 (Basic) +Redis: Basic C0 +SignalR: Free +Static Web: Free +``` + +### Staging + +- **Purpose**: Pre-production testing +- **Configuration**: Production-like with lower capacity +- **Features**: Full feature set, production security + +``` +Cosmos DB: Provisioned (autoscale 400-4000 RU/s) +App Service: S1 (Standard) +Redis: Standard C0 +SignalR: Standard S1 +Static Web: Standard +``` + +### Production + +- **Purpose**: Live user traffic +- **Configuration**: High availability, redundancy +- **Features**: Full feature set, strict security + +``` +Cosmos DB: Provisioned (autoscale 400-4000 RU/s) +App Service: P1v3 (Premium) +Redis: Standard C1 +SignalR: Standard S1 +Static Web: Standard +``` + +--- + +## Security Configuration + +### Key Vault Integration + +All secrets are stored in Azure Key Vault and referenced via managed identity: + +```csharp +// appsettings.json references +{ + "CosmosDb": { + "ConnectionString": "@Microsoft.KeyVault(VaultName=kv-lum-dev;SecretName=cosmosdb-connection)" + } +} +``` + +### Managed Identity + +All compute resources use system-assigned managed identity: + +1. **App Service** → Key Vault (secrets) +2. **App Service** → Cosmos DB (data access) +3. **Function Apps** → Key Vault, Cosmos DB, Storage + +### Network Security + +| Resource | Public Access | Notes | +|----------|---------------|-------| +| App Service | Enabled | HTTPS only | +| Cosmos DB | Enabled | Firewall rules recommended | +| Storage | Enabled | Private endpoints optional | +| Key Vault | Enabled | RBAC authorization | + +### RBAC Roles + +| Principal | Resource | Role | +|-----------|----------|------| +| App Service MI | Key Vault | Key Vault Secrets User | +| App Service MI | Cosmos DB | Cosmos DB Data Contributor | +| App Service MI | Storage | Storage Blob Data Contributor | +| Function App MI | Service Bus | Azure Service Bus Data Sender/Receiver | + +--- + +## Cost Estimation + +### Development Environment (Monthly) + +| Resource | SKU | Estimated Cost | +|----------|-----|----------------| +| Cosmos DB | Serverless | ~$5-20 | +| App Service | B1 | ~$13 | +| Redis | Basic C0 | ~$16 | +| SignalR | Free | $0 | +| Static Web App | Free | $0 | +| Storage | Standard LRS | ~$1 | +| Service Bus | Basic | ~$0.05 | +| Key Vault | Standard | ~$0.03 | +| App Config | Free | $0 | +| Log Analytics | Pay-as-you-go | ~$2 | +| **Total** | | **~$40-60/month** | + +### Production Environment (Monthly) + +| Resource | SKU | Estimated Cost | +|----------|-----|----------------| +| Cosmos DB | 400-4000 RU/s | ~$25-100 | +| App Service | P1v3 | ~$130 | +| Redis | Standard C1 | ~$80 | +| SignalR | Standard S1 | ~$50 | +| Static Web App | Standard | ~$9 | +| Storage | Standard GRS | ~$5 | +| Service Bus | Standard | ~$10 | +| Key Vault | Standard | ~$0.03 | +| App Config | Standard | ~$1 | +| Log Analytics | Pay-as-you-go | ~$10 | +| **Total** | | **~$320-400/month** | + +*Note: Costs are estimates and may vary based on usage.* + +--- + +## Monitoring and Observability + +### Application Insights + +Configured for all compute resources: + +- **Distributed tracing** across API and Functions +- **Live metrics** for real-time monitoring +- **Availability tests** for endpoint monitoring +- **Smart detection** for anomaly alerts + +### Log Analytics Queries + +```kusto +// API request latency (95th percentile) +requests +| where timestamp > ago(1h) +| summarize percentile(duration, 95) by bin(timestamp, 5m) +| render timechart + +// Error rate by operation +requests +| where timestamp > ago(24h) +| summarize total = count(), errors = countif(success == false) by operation_Name +| extend errorRate = errors * 100.0 / total +| order by errorRate desc + +// Cosmos DB RU consumption +AzureDiagnostics +| where ResourceProvider == "MICROSOFT.DOCUMENTDB" +| where Category == "DataPlaneRequests" +| summarize sum(todouble(requestCharge_s)) by bin(TimeGenerated, 5m) +| render timechart +``` + +### Alerts (Recommended) + +| Alert | Condition | Severity | +|-------|-----------|----------| +| API Error Rate | > 5% errors in 5 min | Critical | +| Response Time | P95 > 2 seconds | Warning | +| Cosmos DB Throttling | 429 errors > 10 in 5 min | Warning | +| Function Failures | Any failure | Warning | + +--- + +## Troubleshooting + +### Common Issues + +#### Deployment Fails - Resource Provider Not Registered + +``` +Error: The subscription is not registered to use namespace 'Microsoft.DocumentDB' +``` + +**Solution:** +```bash +az provider register --namespace Microsoft.DocumentDB +az provider wait --namespace Microsoft.DocumentDB --timeout 300 +``` + +#### Cosmos DB Connection Issues + +``` +Error: Unable to connect to Cosmos DB +``` + +**Solution:** +1. Verify managed identity has `Cosmos DB Data Contributor` role +2. Check firewall allows Azure services +3. Verify connection string format + +#### App Service Health Check Fails + +``` +Error: Health check endpoint returning 500 +``` + +**Solution:** +1. Check Application Insights for exceptions +2. Verify app settings are configured +3. Check Key Vault access + +### Logs and Diagnostics + +```bash +# View App Service logs +az webapp log tail --name app-lum-dev-api --resource-group rg-lum-dev + +# View Function App logs +az functionapp logs show --name func-lum-dev-sync --resource-group rg-lum-dev + +# Query Application Insights +az monitor app-insights query \ + --app appi-lum-dev \ + --analytics-query "requests | take 10" +``` + +--- + +## Related Documents + +- [Project Overview](./PROJECT-OVERVIEW.md) +- [Architecture Overview](./ARCHITECTURE.md) +- [Development Roadmap](./ROADMAP.md) +- [ADR-003: Azure as Cloud Platform](./adr/ADR-003-azure-cloud-platform.md) +- [ADR-007: Bicep with AVMs for IaC](./adr/ADR-007-bicep-avm-iac.md) +- [Local Development Guide](./DEVELOPMENT.md) (coming soon) + +--- + +## Document History + +| Version | Date | Author | Changes | +|---------|------|--------|---------| +| 1.0.0 | 2025-12-21 | Luminous Team | Initial infrastructure documentation | diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index bf26658..90d2dd6 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,8 +1,8 @@ # Luminous Development Roadmap -> **Document Version:** 2.0.0 +> **Document Version:** 2.1.0 > **Last Updated:** 2025-12-21 -> **Status:** Draft +> **Status:** Active > **TOGAF Phase:** Phase E/F (Opportunities, Solutions & Migration Planning) --- @@ -100,15 +100,15 @@ Phase 6: Intelligence & Ecosystem ### Phase Summary -| Phase | Name | Focus | Key Deliverables | -|-------|------|-------|------------------| -| **0** | Foundation | Infrastructure | Azure IaC, .NET solution, Angular shell, Passwordless Auth | -| **1** | Core Platform | Multi-tenancy | Family sign-up, device linking, CosmosDB, web MVP | -| **2** | Display & Calendar | Calendar visibility | Display app, calendar integration, SignalR sync | -| **3** | Native Mobile | Mobile apps | iOS (Swift), Android (Kotlin), push notifications | -| **4** | Task Management | Chores and routines | Task creation, completion tracking, rewards | -| **5** | Household Features | Expanded features | Profiles, meals, lists, caregiver portal | -| **6** | Intelligence & Ecosystem | AI and extensions | Magic import, suggestions, third-party APIs | +| Phase | Name | Focus | Key Deliverables | Status | +|-------|------|-------|------------------|--------| +| **0** | Foundation | Infrastructure | Azure IaC, .NET solution, Angular shell, Passwordless Auth | 🟡 In Progress (0.1 Complete) | +| **1** | Core Platform | Multi-tenancy | Family sign-up, device linking, CosmosDB, web MVP | ⬜ Not Started | +| **2** | Display & Calendar | Calendar visibility | Display app, calendar integration, SignalR sync | ⬜ Not Started | +| **3** | Native Mobile | Mobile apps | iOS (Swift), Android (Kotlin), push notifications | ⬜ Not Started | +| **4** | Task Management | Chores and routines | Task creation, completion tracking, rewards | ⬜ Not Started | +| **5** | Household Features | Expanded features | Profiles, meals, lists, caregiver portal | ⬜ Not Started | +| **6** | Intelligence & Ecosystem | AI and extensions | Magic import, suggestions, third-party APIs | ⬜ Not Started | --- @@ -120,15 +120,32 @@ Establish the Azure infrastructure, .NET backend, Angular frontend, and developm ### Scope -#### 0.1 Azure Infrastructure (Bicep with AVMs) - -- [ ] **0.1.1** Create Bicep modules for all Azure resources using AVMs -- [ ] **0.1.2** Configure Cosmos DB with required containers -- [ ] **0.1.3** Set up in-house identity service with WebAuthn/passkey support -- [ ] **0.1.4** Configure App Service for .NET API -- [ ] **0.1.5** Set up Azure Static Web Apps for Angular -- [ ] **0.1.6** Configure Key Vault for secrets -- [ ] **0.1.7** Set up environment parameter files (dev, staging, prod) +#### 0.1 Azure Infrastructure (Bicep with AVMs) ✅ COMPLETED + +- [x] **0.1.1** Create Bicep modules for all Azure resources using AVMs + - *Implemented: main.bicep using 13 AVMs directly from the public Bicep registry (br/public:avm/res/...)* + - *Resources: resource-group, log-analytics, app-insights, key-vault, app-configuration, cosmos-db, storage-account, redis-cache, service-bus, signalr, app-service-plan, web-site, static-web-app* +- [x] **0.1.2** Configure Cosmos DB with required containers + - *Implemented: 11 containers via AVM (families, users, events, chores, devices, routines, lists, meals, completions, invitations, credentials)* +- [x] **0.1.3** Set up in-house identity service with WebAuthn/passkey support + - *Implemented: credentials container in Cosmos DB for WebAuthn credential storage; App Service configured for passwordless auth integration* +- [x] **0.1.4** Configure App Service for .NET API + - *Implemented: Using br/public:avm/res/web/site with .NET 9, managed identity, health checks* +- [x] **0.1.5** Set up Azure Static Web Apps for Angular + - *Implemented: Using br/public:avm/res/web/static-site with staging environment support* +- [x] **0.1.6** Configure Key Vault for secrets + - *Implemented: Using br/public:avm/res/key-vault/vault with RBAC authorization, soft delete* +- [x] **0.1.7** Set up environment parameter files (dev, staging, prod) + - *Implemented: dev.bicepparam, staging.bicepparam, prod.bicepparam with environment-appropriate SKUs and settings* + +**Additional deliverables:** +- [x] Deployment scripts (deploy.sh, deploy.ps1) for automated deployment +- [x] Azure Infrastructure documentation (docs/AZURE-INFRASTRUCTURE.md) +- [x] Function Apps for sync and import processing +- [x] Service Bus with queues for async messaging +- [x] SignalR Service for real-time sync +- [x] Redis Cache for session management +- [x] Log Analytics and Application Insights for monitoring #### 0.2 .NET Solution Structure @@ -610,3 +627,6 @@ These can be developed in parallel after Phase 0: | Version | Date | Author | Changes | |---------|------|--------|---------| | 1.0.0 | 2025-12-21 | Luminous Team | Initial roadmap | +| 2.0.0 | 2025-12-21 | Luminous Team | Updated for Azure/.NET/Angular stack | +| 2.1.0 | 2025-12-21 | Luminous Team | Phase 0.1 Azure Infrastructure completed | +| 2.1.1 | 2025-12-21 | Luminous Team | Refactored to use AVMs directly from public registry | diff --git a/infra/bicep/main.bicep b/infra/bicep/main.bicep new file mode 100644 index 0000000..0fbc041 --- /dev/null +++ b/infra/bicep/main.bicep @@ -0,0 +1,482 @@ +// ============================================================================= +// Luminous - Main Infrastructure Deployment +// ============================================================================= +// Deploys Luminous Azure infrastructure using Azure Verified Modules (AVMs) +// directly from the public Bicep registry. +// +// Usage: +// az deployment sub create \ +// --location \ +// --template-file main.bicep \ +// --parameters @parameters/dev.bicepparam +// +// TOGAF Principle: TP-4 - Infrastructure as Code +// ADR-007: Bicep with AVMs for IaC +// ============================================================================= + +targetScope = 'subscription' + +// ============================================================================= +// Parameters +// ============================================================================= + +@description('Environment name (dev, staging, prod)') +@allowed(['dev', 'staging', 'prod']) +param environment string + +@description('Azure region for deployment') +param location string = 'eastus2' + +@description('Project name used for resource naming') +param projectName string = 'luminous' + +@description('Short project prefix for resource naming (max 3 chars)') +@maxLength(3) +param projectPrefix string = 'lum' + +@description('Tags to apply to all resources') +param tags object = { + Project: 'Luminous' + Environment: environment + ManagedBy: 'Bicep' + Repository: 'trickpatty/Luminous' +} + +// Cosmos DB Configuration +@description('Enable serverless mode for Cosmos DB (recommended for dev)') +param cosmosDbServerless bool = environment == 'dev' + +@description('Cosmos DB consistency level') +@allowed(['Eventual', 'Session', 'BoundedStaleness', 'Strong', 'ConsistentPrefix']) +param cosmosDbConsistencyLevel string = 'Session' + +// App Service Configuration +@description('App Service Plan SKU') +param appServiceSkuName string = environment == 'prod' ? 'P1v3' : 'B1' + +// Redis Configuration +@description('Redis Cache SKU') +@allowed(['Basic', 'Standard', 'Premium']) +param redisSku string = environment == 'prod' ? 'Standard' : 'Basic' + +@description('Redis Cache Capacity') +param redisCapacity int = environment == 'prod' ? 1 : 0 + +// SignalR Configuration +@description('SignalR Service SKU') +param signalRSku string = environment == 'prod' ? 'Standard_S1' : 'Free_F1' + +// Static Web App Configuration +@description('Static Web App SKU') +@allowed(['Free', 'Standard']) +param staticWebAppSku string = environment == 'prod' ? 'Standard' : 'Free' + +// ============================================================================= +// Variables +// ============================================================================= + +var resourceGroupName = 'rg-${projectPrefix}-${environment}' +var namingPrefix = '${projectPrefix}-${environment}' + +// Resource naming following Azure naming conventions +var names = { + cosmosDb: 'cosmos-${namingPrefix}' + storageAccount: 'st${projectPrefix}${environment}' + keyVault: 'kv-${namingPrefix}' + appConfig: 'appcs-${namingPrefix}' + appServicePlan: 'asp-${namingPrefix}' + appService: 'app-${namingPrefix}-api' + functionAppSync: 'func-${namingPrefix}-sync' + functionAppImport: 'func-${namingPrefix}-import' + signalR: 'sigr-${namingPrefix}' + serviceBus: 'sb-${namingPrefix}' + redis: 'redis-${namingPrefix}' + staticWebApp: 'stapp-${namingPrefix}' + logAnalytics: 'log-${namingPrefix}' + appInsights: 'appi-${namingPrefix}' +} + +// Cosmos DB container configurations for multi-tenant data isolation +var cosmosContainers = [ + { name: 'families', partitionKeyPath: '/id' } + { name: 'users', partitionKeyPath: '/familyId' } + { name: 'events', partitionKeyPath: '/familyId' } + { name: 'chores', partitionKeyPath: '/familyId' } + { name: 'devices', partitionKeyPath: '/familyId' } + { name: 'routines', partitionKeyPath: '/familyId' } + { name: 'lists', partitionKeyPath: '/familyId' } + { name: 'meals', partitionKeyPath: '/familyId' } + { name: 'completions', partitionKeyPath: '/familyId' } + { name: 'invitations', partitionKeyPath: '/familyId' } + { name: 'credentials', partitionKeyPath: '/userId' } +] + +// ============================================================================= +// Resource Group (using AVM) +// ============================================================================= + +module rg 'br/public:avm/res/resources/resource-group:0.4.0' = { + name: 'deploy-resource-group' + params: { + name: resourceGroupName + location: location + tags: tags + } +} + +// ============================================================================= +// Monitoring (Deploy first - other resources depend on these) +// ============================================================================= + +module logAnalytics 'br/public:avm/res/operational-insights/workspace:0.9.0' = { + name: 'deploy-log-analytics' + scope: resourceGroup(resourceGroupName) + params: { + name: names.logAnalytics + location: location + tags: tags + dataRetention: environment == 'prod' ? 90 : 30 + skuName: 'PerGB2018' + } + dependsOn: [rg] +} + +module appInsights 'br/public:avm/res/insights/component:0.4.1' = { + name: 'deploy-app-insights' + scope: resourceGroup(resourceGroupName) + params: { + name: names.appInsights + location: location + tags: tags + workspaceResourceId: logAnalytics.outputs.resourceId + applicationType: 'web' + } + dependsOn: [rg] +} + +// ============================================================================= +// Security +// ============================================================================= + +module keyVault 'br/public:avm/res/key-vault/vault:0.9.0' = { + name: 'deploy-key-vault' + scope: resourceGroup(resourceGroupName) + params: { + name: names.keyVault + location: location + tags: tags + sku: 'standard' + enableRbacAuthorization: true + enableSoftDelete: environment == 'prod' + softDeleteRetentionInDays: 90 + enablePurgeProtection: environment == 'prod' + } + dependsOn: [rg] +} + +module appConfig 'br/public:avm/res/app-configuration/configuration-store:0.5.1' = { + name: 'deploy-app-config' + scope: resourceGroup(resourceGroupName) + params: { + name: names.appConfig + location: location + tags: tags + sku: environment == 'prod' ? 'Standard' : 'Free' + } + dependsOn: [rg] +} + +// ============================================================================= +// Data Services +// ============================================================================= + +module cosmosDb 'br/public:avm/res/document-db/database-account:0.8.1' = { + name: 'deploy-cosmos-db' + scope: resourceGroup(resourceGroupName) + params: { + name: names.cosmosDb + location: location + tags: tags + capabilitiesToAdd: cosmosDbServerless ? ['EnableServerless'] : [] + defaultConsistencyLevel: cosmosDbConsistencyLevel + locations: [ + { + locationName: location + failoverPriority: 0 + isZoneRedundant: false + } + ] + sqlDatabases: [ + { + name: projectName + containers: [for container in cosmosContainers: { + name: container.name + paths: [container.partitionKeyPath] + }] + } + ] + } + dependsOn: [rg] +} + +module storageAccount 'br/public:avm/res/storage/storage-account:0.14.3' = { + name: 'deploy-storage-account' + scope: resourceGroup(resourceGroupName) + params: { + name: names.storageAccount + location: location + tags: tags + skuName: environment == 'prod' ? 'Standard_GRS' : 'Standard_LRS' + kind: 'StorageV2' + allowBlobPublicAccess: false + blobServices: { + containers: [ + { name: 'avatars', publicAccess: 'None' } + { name: 'recipes', publicAccess: 'None' } + { name: 'imports', publicAccess: 'None' } + { name: 'exports', publicAccess: 'None' } + ] + deleteRetentionPolicy: { + enabled: true + days: 7 + } + } + } + dependsOn: [rg] +} + +module redis 'br/public:avm/res/cache/redis:0.8.0' = { + name: 'deploy-redis-cache' + scope: resourceGroup(resourceGroupName) + params: { + name: names.redis + location: location + tags: tags + skuName: redisSku + capacity: redisCapacity + redisVersion: '6' + minimumTlsVersion: '1.2' + publicNetworkAccess: 'Enabled' + } + dependsOn: [rg] +} + +// ============================================================================= +// Messaging Services +// ============================================================================= + +module serviceBus 'br/public:avm/res/service-bus/namespace:0.10.0' = { + name: 'deploy-service-bus' + scope: resourceGroup(resourceGroupName) + params: { + name: names.serviceBus + location: location + tags: tags + skuObject: { + name: environment == 'prod' ? 'Standard' : 'Basic' + } + queues: [ + { name: 'calendar-sync' } + { name: 'import-processing' } + { name: 'notifications' } + ] + } + dependsOn: [rg] +} + +module signalR 'br/public:avm/res/signal-r-service/signal-r:0.5.0' = { + name: 'deploy-signalr' + scope: resourceGroup(resourceGroupName) + params: { + name: names.signalR + location: location + tags: tags + sku: signalRSku + kind: 'SignalR' + features: [ + { flag: 'ServiceMode', value: 'Default' } + { flag: 'EnableConnectivityLogs', value: 'True' } + ] + } + dependsOn: [rg] +} + +// ============================================================================= +// Compute Services +// ============================================================================= + +module appServicePlan 'br/public:avm/res/web/serverfarm:0.3.0' = { + name: 'deploy-app-service-plan' + scope: resourceGroup(resourceGroupName) + params: { + name: names.appServicePlan + location: location + tags: tags + skuName: appServiceSkuName + skuCapacity: 1 + kind: 'Linux' + reserved: true + } + dependsOn: [rg] +} + +module appService 'br/public:avm/res/web/site:0.11.1' = { + name: 'deploy-app-service' + scope: resourceGroup(resourceGroupName) + params: { + name: names.appService + location: location + tags: tags + kind: 'app,linux' + serverFarmResourceId: appServicePlan.outputs.resourceId + managedIdentities: { + systemAssigned: true + } + siteConfig: { + linuxFxVersion: 'DOTNETCORE|9.0' + alwaysOn: appServiceSkuName != 'F1' && appServiceSkuName != 'D1' + http20Enabled: true + minTlsVersion: '1.2' + ftpsState: 'Disabled' + healthCheckPath: '/health' + } + httpsOnly: true + clientAffinityEnabled: false + appSettingsKeyValuePairs: { + ASPNETCORE_ENVIRONMENT: environment == 'prod' ? 'Production' : 'Development' + APPLICATIONINSIGHTS_CONNECTION_STRING: appInsights.outputs.connectionString + CosmosDb__Endpoint: cosmosDb.outputs.endpoint + CosmosDb__DatabaseName: projectName + SignalR__ConnectionString: signalR.outputs.hostName + AppConfig__Endpoint: appConfig.outputs.endpoint + } + } + dependsOn: [ + cosmosDb + signalR + appConfig + appInsights + ] +} + +module functionAppSync 'br/public:avm/res/web/site:0.11.1' = { + name: 'deploy-function-app-sync' + scope: resourceGroup(resourceGroupName) + params: { + name: names.functionAppSync + location: location + tags: tags + kind: 'functionapp,linux' + serverFarmResourceId: appServicePlan.outputs.resourceId + managedIdentities: { + systemAssigned: true + } + siteConfig: { + linuxFxVersion: 'DOTNET-ISOLATED|9.0' + use32BitWorkerProcess: false + ftpsState: 'Disabled' + minTlsVersion: '1.2' + } + httpsOnly: true + appSettingsKeyValuePairs: { + FUNCTIONS_EXTENSION_VERSION: '~4' + FUNCTIONS_WORKER_RUNTIME: 'dotnet-isolated' + AzureWebJobsStorage: storageAccount.outputs.primaryBlobEndpoint + APPLICATIONINSIGHTS_CONNECTION_STRING: appInsights.outputs.connectionString + CosmosDb__Endpoint: cosmosDb.outputs.endpoint + CosmosDb__DatabaseName: projectName + } + } + dependsOn: [ + cosmosDb + storageAccount + appInsights + ] +} + +module functionAppImport 'br/public:avm/res/web/site:0.11.1' = { + name: 'deploy-function-app-import' + scope: resourceGroup(resourceGroupName) + params: { + name: names.functionAppImport + location: location + tags: tags + kind: 'functionapp,linux' + serverFarmResourceId: appServicePlan.outputs.resourceId + managedIdentities: { + systemAssigned: true + } + siteConfig: { + linuxFxVersion: 'DOTNET-ISOLATED|9.0' + use32BitWorkerProcess: false + ftpsState: 'Disabled' + minTlsVersion: '1.2' + } + httpsOnly: true + appSettingsKeyValuePairs: { + FUNCTIONS_EXTENSION_VERSION: '~4' + FUNCTIONS_WORKER_RUNTIME: 'dotnet-isolated' + AzureWebJobsStorage: storageAccount.outputs.primaryBlobEndpoint + APPLICATIONINSIGHTS_CONNECTION_STRING: appInsights.outputs.connectionString + CosmosDb__Endpoint: cosmosDb.outputs.endpoint + CosmosDb__DatabaseName: projectName + } + } + dependsOn: [ + cosmosDb + storageAccount + appInsights + ] +} + +// ============================================================================= +// Web Hosting +// ============================================================================= + +module staticWebApp 'br/public:avm/res/web/static-site:0.6.0' = { + name: 'deploy-static-web-app' + scope: resourceGroup(resourceGroupName) + params: { + name: names.staticWebApp + location: location + tags: tags + sku: staticWebAppSku + stagingEnvironmentPolicy: 'Enabled' + allowConfigFileUpdates: true + } + dependsOn: [rg] +} + +// ============================================================================= +// Outputs +// ============================================================================= + +output resourceGroupName string = resourceGroupName +output resourceGroupId string = rg.outputs.resourceId + +// Data Services +output cosmosDbEndpoint string = cosmosDb.outputs.endpoint +output cosmosDbAccountName string = cosmosDb.outputs.name +output storageAccountName string = storageAccount.outputs.name +output redisHostName string = redis.outputs.hostName + +// Compute Services +output appServiceUrl string = appService.outputs.defaultHostname +output functionAppSyncUrl string = functionAppSync.outputs.defaultHostname +output functionAppImportUrl string = functionAppImport.outputs.defaultHostname + +// Web Hosting +output staticWebAppUrl string = staticWebApp.outputs.defaultHostname + +// Security +output keyVaultName string = keyVault.outputs.name +output keyVaultUri string = keyVault.outputs.uri +output appConfigEndpoint string = appConfig.outputs.endpoint + +// Messaging +output signalRHostName string = signalR.outputs.hostName +output serviceBusNamespace string = serviceBus.outputs.name + +// Monitoring +output appInsightsConnectionString string = appInsights.outputs.connectionString +output logAnalyticsWorkspaceId string = logAnalytics.outputs.resourceId diff --git a/infra/bicep/parameters/dev.bicepparam b/infra/bicep/parameters/dev.bicepparam new file mode 100644 index 0000000..495702e --- /dev/null +++ b/infra/bicep/parameters/dev.bicepparam @@ -0,0 +1,46 @@ +// ============================================================================= +// Luminous - Development Environment Parameters +// ============================================================================= +// Parameters for deploying Luminous infrastructure to the development +// environment. Uses minimal SKUs to reduce costs. +// +// Usage: +// az deployment sub create \ +// --location eastus2 \ +// --template-file main.bicep \ +// --parameters @parameters/dev.bicepparam +// ============================================================================= + +using '../main.bicep' + +// Environment Configuration +param environment = 'dev' +param location = 'eastus2' +param projectName = 'luminous' +param projectPrefix = 'lum' + +// Tags +param tags = { + Project: 'Luminous' + Environment: 'Development' + ManagedBy: 'Bicep' + Repository: 'trickpatty/Luminous' + CostCenter: 'Development' +} + +// Cosmos DB - Use serverless for dev to reduce costs +param cosmosDbServerless = true +param cosmosDbConsistencyLevel = 'Session' + +// App Service - Basic tier for dev +param appServiceSkuName = 'B1' + +// Redis - Basic tier for dev (smallest available) +param redisSku = 'Basic' +param redisCapacity = 0 + +// SignalR - Free tier for dev +param signalRSku = 'Free_F1' + +// Static Web App - Free tier for dev +param staticWebAppSku = 'Free' diff --git a/infra/bicep/parameters/prod.bicepparam b/infra/bicep/parameters/prod.bicepparam new file mode 100644 index 0000000..4db5f20 --- /dev/null +++ b/infra/bicep/parameters/prod.bicepparam @@ -0,0 +1,47 @@ +// ============================================================================= +// Luminous - Production Environment Parameters +// ============================================================================= +// Parameters for deploying Luminous infrastructure to the production +// environment. Uses high-availability configurations with appropriate SKUs. +// +// Usage: +// az deployment sub create \ +// --location eastus2 \ +// --template-file main.bicep \ +// --parameters @parameters/prod.bicepparam +// ============================================================================= + +using '../main.bicep' + +// Environment Configuration +param environment = 'prod' +param location = 'eastus2' +param projectName = 'luminous' +param projectPrefix = 'lum' + +// Tags +param tags = { + Project: 'Luminous' + Environment: 'Production' + ManagedBy: 'Bicep' + Repository: 'trickpatty/Luminous' + CostCenter: 'Production' + Criticality: 'High' +} + +// Cosmos DB - Provisioned throughput with auto-scale for production +param cosmosDbServerless = false +param cosmosDbConsistencyLevel = 'Session' + +// App Service - Premium tier for production (better performance, scaling) +param appServiceSkuName = 'P1v3' + +// Redis - Standard tier for production with higher capacity +param redisSku = 'Standard' +param redisCapacity = 1 + +// SignalR - Standard tier for production +param signalRSku = 'Standard_S1' + +// Static Web App - Standard for production +param staticWebAppSku = 'Standard' diff --git a/infra/bicep/parameters/staging.bicepparam b/infra/bicep/parameters/staging.bicepparam new file mode 100644 index 0000000..1a777df --- /dev/null +++ b/infra/bicep/parameters/staging.bicepparam @@ -0,0 +1,46 @@ +// ============================================================================= +// Luminous - Staging Environment Parameters +// ============================================================================= +// Parameters for deploying Luminous infrastructure to the staging +// environment. Uses production-like configuration for testing. +// +// Usage: +// az deployment sub create \ +// --location eastus2 \ +// --template-file main.bicep \ +// --parameters @parameters/staging.bicepparam +// ============================================================================= + +using '../main.bicep' + +// Environment Configuration +param environment = 'staging' +param location = 'eastus2' +param projectName = 'luminous' +param projectPrefix = 'lum' + +// Tags +param tags = { + Project: 'Luminous' + Environment: 'Staging' + ManagedBy: 'Bicep' + Repository: 'trickpatty/Luminous' + CostCenter: 'Staging' +} + +// Cosmos DB - Provisioned throughput for staging (production-like) +param cosmosDbServerless = false +param cosmosDbConsistencyLevel = 'Session' + +// App Service - Standard tier for staging +param appServiceSkuName = 'S1' + +// Redis - Standard tier for staging +param redisSku = 'Standard' +param redisCapacity = 0 + +// SignalR - Standard tier for staging +param signalRSku = 'Standard_S1' + +// Static Web App - Standard for staging +param staticWebAppSku = 'Standard' diff --git a/infra/scripts/deploy.ps1 b/infra/scripts/deploy.ps1 new file mode 100644 index 0000000..17fe065 --- /dev/null +++ b/infra/scripts/deploy.ps1 @@ -0,0 +1,214 @@ +<# +.SYNOPSIS + Luminous - Azure Infrastructure Deployment Script (PowerShell) + +.DESCRIPTION + Deploys Luminous Azure infrastructure using Bicep templates. + +.PARAMETER Environment + The target environment (dev, staging, prod) + +.PARAMETER WhatIf + Preview changes without deploying + +.PARAMETER Location + Azure region for deployment (default: eastus2) + +.EXAMPLE + .\deploy.ps1 -Environment dev + +.EXAMPLE + .\deploy.ps1 -Environment staging -WhatIf + +.EXAMPLE + .\deploy.ps1 -Environment prod -Location westus2 +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true, Position = 0)] + [ValidateSet('dev', 'staging', 'prod')] + [string]$Environment, + + [Parameter()] + [switch]$WhatIf, + + [Parameter()] + [string]$Location = 'eastus2' +) + +# Script variables +$ErrorActionPreference = 'Stop' +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$BicepDir = Join-Path $ScriptDir '..\bicep' +$DeploymentName = "luminous-infra-$(Get-Date -Format 'yyyyMMdd-HHmmss')" + +# ============================================================================= +# Functions +# ============================================================================= + +function Write-Header { + param([string]$Message) + Write-Host "" + Write-Host "=============================================================================" -ForegroundColor Blue + Write-Host " $Message" -ForegroundColor Blue + Write-Host "=============================================================================" -ForegroundColor Blue + Write-Host "" +} + +function Write-Success { + param([string]$Message) + Write-Host "✓ $Message" -ForegroundColor Green +} + +function Write-Warning { + param([string]$Message) + Write-Host "⚠ $Message" -ForegroundColor Yellow +} + +function Write-Error { + param([string]$Message) + Write-Host "✗ $Message" -ForegroundColor Red +} + +function Write-Info { + param([string]$Message) + Write-Host "ℹ $Message" -ForegroundColor Blue +} + +function Test-Prerequisites { + Write-Header "Checking Prerequisites" + + # Check Azure CLI + try { + $azVersion = az version 2>$null | ConvertFrom-Json + Write-Success "Azure CLI is installed (v$($azVersion.'azure-cli'))" + } + catch { + Write-Error "Azure CLI is not installed. Please install it first." + Write-Host " https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" + exit 1 + } + + # Check if logged in + try { + $account = az account show 2>$null | ConvertFrom-Json + Write-Success "Logged in to Azure" + Write-Info "Current subscription: $($account.name)" + } + catch { + Write-Error "Not logged in to Azure. Please run 'az login' first." + exit 1 + } + + # Check Bicep + try { + $bicepVersion = az bicep version 2>$null + Write-Success "Bicep CLI is available" + } + catch { + Write-Warning "Bicep CLI not found. Installing..." + az bicep install + Write-Success "Bicep CLI installed" + } +} + +function Test-BicepTemplates { + Write-Header "Validating Bicep Templates" + + $mainBicep = Join-Path $BicepDir 'main.bicep' + + Write-Info "Building main.bicep..." + + try { + $result = az bicep build --file $mainBicep --stdout 2>&1 + Write-Success "Bicep templates are valid" + } + catch { + Write-Error "Bicep validation failed" + az bicep build --file $mainBicep + exit 1 + } +} + +function Deploy-Infrastructure { + param( + [string]$Env, + [bool]$IsWhatIf + ) + + Write-Header "Deploying to $Env Environment" + + $paramFile = Join-Path $BicepDir "parameters\$Env.bicepparam" + $mainBicep = Join-Path $BicepDir 'main.bicep' + + # Check parameter file exists + if (-not (Test-Path $paramFile)) { + Write-Error "Parameter file not found: $paramFile" + exit 1 + } + Write-Success "Using parameter file: $paramFile" + + Write-Info "Starting deployment..." + Write-Host "" + + try { + if ($IsWhatIf) { + Write-Info "Running what-if analysis..." + az deployment sub what-if ` + --location $Location ` + --template-file $mainBicep ` + --parameters "@$paramFile" + + Write-Success "What-if analysis completed" + } + else { + az deployment sub create ` + --name $DeploymentName ` + --location $Location ` + --template-file $mainBicep ` + --parameters "@$paramFile" + + Write-Success "Deployment completed successfully" + Write-Info "Deployment name: $DeploymentName" + } + } + catch { + Write-Error "Deployment failed: $_" + exit 1 + } +} + +function Show-Outputs { + Write-Header "Deployment Outputs" + + try { + az deployment sub show ` + --name $DeploymentName ` + --query properties.outputs ` + -o table + } + catch { + Write-Warning "Could not retrieve outputs" + } +} + +# ============================================================================= +# Main +# ============================================================================= + +Write-Header "Luminous Infrastructure Deployment" +Write-Host "Environment: $Environment" +Write-Host "Location: $Location" +Write-Host "What-If: $WhatIf" + +Test-Prerequisites +Test-BicepTemplates +Deploy-Infrastructure -Env $Environment -IsWhatIf $WhatIf + +if (-not $WhatIf) { + Show-Outputs +} + +Write-Header "Deployment Complete" +Write-Success "Infrastructure deployment finished for $Environment environment" diff --git a/infra/scripts/deploy.sh b/infra/scripts/deploy.sh new file mode 100755 index 0000000..e36672a --- /dev/null +++ b/infra/scripts/deploy.sh @@ -0,0 +1,251 @@ +#!/bin/bash +# ============================================================================= +# Luminous - Azure Infrastructure Deployment Script +# ============================================================================= +# Deploys Luminous Azure infrastructure using Bicep templates. +# +# Usage: +# ./deploy.sh [--what-if] +# +# Examples: +# ./deploy.sh dev # Deploy to development +# ./deploy.sh staging # Deploy to staging +# ./deploy.sh prod # Deploy to production +# ./deploy.sh dev --what-if # Preview changes without deploying +# +# Prerequisites: +# - Azure CLI installed and logged in (az login) +# - Correct subscription selected (az account set -s ) +# ============================================================================= + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BICEP_DIR="$SCRIPT_DIR/../bicep" + +# Default values +LOCATION="eastus2" +DEPLOYMENT_NAME="luminous-infra-$(date +%Y%m%d-%H%M%S)" + +# ============================================================================= +# Functions +# ============================================================================= + +print_header() { + echo "" + echo -e "${BLUE}=============================================================================${NC}" + echo -e "${BLUE} $1${NC}" + echo -e "${BLUE}=============================================================================${NC}" + echo "" +} + +print_success() { + echo -e "${GREEN}✓ $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠ $1${NC}" +} + +print_error() { + echo -e "${RED}✗ $1${NC}" +} + +print_info() { + echo -e "${BLUE}ℹ $1${NC}" +} + +usage() { + echo "Usage: $0 [options]" + echo "" + echo "Environments:" + echo " dev Deploy to development environment" + echo " staging Deploy to staging environment" + echo " prod Deploy to production environment" + echo "" + echo "Options:" + echo " --what-if Preview changes without deploying" + echo " --location Azure region (default: eastus2)" + echo " --help Show this help message" + echo "" + echo "Examples:" + echo " $0 dev" + echo " $0 staging --what-if" + echo " $0 prod --location westus2" + exit 1 +} + +check_prerequisites() { + print_header "Checking Prerequisites" + + # Check Azure CLI + if ! command -v az &> /dev/null; then + print_error "Azure CLI is not installed. Please install it first." + echo " https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" + exit 1 + fi + print_success "Azure CLI is installed" + + # Check if logged in + if ! az account show &> /dev/null; then + print_error "Not logged in to Azure. Please run 'az login' first." + exit 1 + fi + print_success "Logged in to Azure" + + # Check Bicep + if ! az bicep version &> /dev/null; then + print_warning "Bicep CLI not found. Installing..." + az bicep install + fi + print_success "Bicep CLI is available" + + # Show current subscription + SUBSCRIPTION=$(az account show --query name -o tsv) + print_info "Current subscription: $SUBSCRIPTION" +} + +validate_environment() { + case $1 in + dev|staging|prod) + return 0 + ;; + *) + print_error "Invalid environment: $1" + usage + ;; + esac +} + +validate_bicep() { + print_header "Validating Bicep Templates" + + print_info "Building main.bicep..." + if az bicep build --file "$BICEP_DIR/main.bicep" --stdout > /dev/null 2>&1; then + print_success "Bicep templates are valid" + else + print_error "Bicep validation failed" + az bicep build --file "$BICEP_DIR/main.bicep" + exit 1 + fi +} + +deploy_infrastructure() { + local env=$1 + local what_if=$2 + local param_file="$BICEP_DIR/parameters/${env}.bicepparam" + + print_header "Deploying to $env Environment" + + # Check parameter file exists + if [[ ! -f "$param_file" ]]; then + print_error "Parameter file not found: $param_file" + exit 1 + fi + print_success "Using parameter file: $param_file" + + # Build deployment command + local deploy_cmd="az deployment sub create \ + --name $DEPLOYMENT_NAME \ + --location $LOCATION \ + --template-file $BICEP_DIR/main.bicep \ + --parameters @$param_file" + + if [[ "$what_if" == "true" ]]; then + print_info "Running what-if analysis..." + deploy_cmd="az deployment sub what-if \ + --location $LOCATION \ + --template-file $BICEP_DIR/main.bicep \ + --parameters @$param_file" + fi + + print_info "Starting deployment..." + echo "" + + if eval $deploy_cmd; then + if [[ "$what_if" == "true" ]]; then + print_success "What-if analysis completed" + else + print_success "Deployment completed successfully" + print_info "Deployment name: $DEPLOYMENT_NAME" + fi + else + print_error "Deployment failed" + exit 1 + fi +} + +show_outputs() { + local env=$1 + + print_header "Deployment Outputs" + + az deployment sub show \ + --name $DEPLOYMENT_NAME \ + --query properties.outputs \ + -o table 2>/dev/null || print_warning "Could not retrieve outputs" +} + +# ============================================================================= +# Main +# ============================================================================= + +# Parse arguments +ENVIRONMENT="" +WHAT_IF="false" + +while [[ $# -gt 0 ]]; do + case $1 in + dev|staging|prod) + ENVIRONMENT=$1 + shift + ;; + --what-if) + WHAT_IF="true" + shift + ;; + --location) + LOCATION=$2 + shift 2 + ;; + --help|-h) + usage + ;; + *) + print_error "Unknown option: $1" + usage + ;; + esac +done + +# Validate environment was provided +if [[ -z "$ENVIRONMENT" ]]; then + print_error "Environment is required" + usage +fi + +# Run deployment +print_header "Luminous Infrastructure Deployment" +echo "Environment: $ENVIRONMENT" +echo "Location: $LOCATION" +echo "What-If: $WHAT_IF" + +validate_environment "$ENVIRONMENT" +check_prerequisites +validate_bicep +deploy_infrastructure "$ENVIRONMENT" "$WHAT_IF" + +if [[ "$WHAT_IF" == "false" ]]; then + show_outputs "$ENVIRONMENT" +fi + +print_header "Deployment Complete" +print_success "Infrastructure deployment finished for $ENVIRONMENT environment"